From 3f43e40ed1a1e9f66b3ad2862da9dbc4b710c712 Mon Sep 17 00:00:00 2001
From: nishiwei <13126973358@163.com>
Date: Fri, 23 Oct 2026 14:09:47 +0800
Subject: [PATCH] 智云3.0机场1适配内容提交
---
ard-sample/src/main/java/com/ard/sample/component/websocket/handler/DockWebSocketHandler.java | 84 +++++++++++++++++++++++++++++++++++------
1 files changed, 71 insertions(+), 13 deletions(-)
diff --git a/ard-sample/src/main/java/com/ard/sample/component/websocket/handler/DockWebSocketHandler.java b/ard-sample/src/main/java/com/ard/sample/component/websocket/handler/DockWebSocketHandler.java
index 36ab8cc..6ad0a20 100644
--- a/ard-sample/src/main/java/com/ard/sample/component/websocket/handler/DockWebSocketHandler.java
+++ b/ard-sample/src/main/java/com/ard/sample/component/websocket/handler/DockWebSocketHandler.java
@@ -4,7 +4,6 @@
import com.ard.sample.component.websocket.enums.MessageType;
import com.ard.sample.component.websocket.model.Payload;
import com.ard.sample.component.websocket.model.WebSocketMessage;
-import com.ard.sample.manage.service.IControlService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -19,6 +18,7 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
@@ -32,9 +32,6 @@
@Autowired
private LiveStreamHandler _liveStreamService;
- @Autowired
- private IControlService _controlService;
-
// ====================== 心跳相关常量(可配置化建议放到配置文件) ======================
/** 心跳间隔(秒):客户端需每隔此时间发送一次PING消息 */
private static final long HEARTBEAT_INTERVAL = 30;
@@ -42,6 +39,12 @@
private static final long HEARTBEAT_TIMEOUT = 90;
/** 心跳检测任务间隔(秒):定时扫描超时连接 */
private static final long HEARTBEAT_CHECK_INTERVAL = 10;
+ /**
+ * 停流延迟(秒):页面刷新或网络抖动会先断开旧连接再建立新连接,
+ * 断开时立即 live_stop 会让新页面需要重新起流,视频出现空档甚至丢失。
+ * 延迟期间重新订阅则取消停止。
+ */
+ private static final long LIVE_STOP_DELAY_SECONDS = 30;
// ====================== 核心存储(线程安全) ======================
/** 用户会话映射:userId -> WebSocketSession */
@@ -52,12 +55,21 @@
private final Map<WebSocketSession, Object> sessionLocks = new ConcurrentHashMap<>();
/** 用户最后心跳时间:userId -> 最后心跳时间戳(毫秒) */
private final Map<String, Long> userLastHeartbeatTime = new ConcurrentHashMap<>();
+ /** 待执行的延迟停流任务:设备SN -> 定时任务 */
+ private final Map<String, ScheduledFuture<?>> pendingLiveStops = new ConcurrentHashMap<>();
// ====================== 线程池 ======================
/** 心跳检测线程池(单线程守护线程) */
private final ScheduledExecutorService heartbeatCheckExecutor = Executors.newSingleThreadScheduledExecutor(runnable -> {
Thread thread = new Thread(runnable, "websocket-heartbeat-check-thread");
thread.setDaemon(true); // 守护线程,不阻塞JVM退出
+ return thread;
+ });
+
+ /** 延迟停流线程池(单线程守护线程),停流可能走 HTTP,独立线程避免阻塞心跳检测 */
+ private final ScheduledExecutorService liveStopExecutor = Executors.newSingleThreadScheduledExecutor(runnable -> {
+ Thread thread = new Thread(runnable, "websocket-live-stop-thread");
+ thread.setDaemon(true);
return thread;
});
@@ -192,6 +204,10 @@
if (isSubscribe) {
// 订阅操作:不存在则创建,存在则添加用户
subscriptions.computeIfAbsent(subscriptionKey, k -> ConcurrentHashMap.newKeySet()).add(userId);
+ // 页面刷新会先断连再重连,新订阅到达时取消上一次断连触发的延迟停流
+ if (MessageType.LIVESTREAM.getType().equals(type)) {
+ cancelPendingLiveStop(sn);
+ }
// log.info("用户[{}]订阅设备[{}]的[{}]类型消息", userId, sn, type);
// // 直播类型订阅:触发开启直播
@@ -206,12 +222,12 @@
subscriberIds.remove(userId);
log.info("用户[{}]取消订阅设备[{}]的[{}]类型消息", userId, sn, type);
- // 订阅组为空时移除,并停止直播(直播类型)
+ // 订阅组为空时移除,并延迟停止直播(直播类型)。
+ // 不立即停流:页面刷新会先断连再重连,立即停流会导致视频长时间黑屏。
if (subscriberIds.isEmpty()) {
subscriptions.remove(subscriptionKey);
if (MessageType.LIVESTREAM.getType().equals(type)) {
- _liveStreamService.StopLiveStreaming(sn);
- log.info("设备[{}]直播流已停止(无订阅用户)", sn);
+ scheduleStopLiveStream(sn);
}
}
}
@@ -287,10 +303,13 @@
// 线程安全发送消息
try {
- if(MessageType.LIVESTREAM.getType().equals(type)) {
+ // 指定了接收用户时只推给该用户;uId 为空(如后台定时任务起流)时推给全部订阅者
+ if (MessageType.LIVESTREAM.getType().equals(type) && uId != null && !uId.isEmpty()) {
WebSocketSession liveSession = userSessions.get(uId);
- sendMessage(liveSession, content);
- log.info("单独向用户[{}]推送设备[{}]的[{}]类型消息:{}", uId, sn, type, content);
+ if (liveSession != null && liveSession.isOpen()) {
+ sendMessage(liveSession, content);
+ log.info("单独向用户[{}]推送设备[{}]的[{}]类型消息:{}", uId, sn, type, content);
+ }
return;
}
sendMessage(session, content);
@@ -371,17 +390,52 @@
if (subscriberIds.remove(userId)) {
log.debug("用户[{}]已移除订阅:{}", userId, entry.getKey());
- // 订阅组为空时移除,并停止直播(直播类型)
+ // 订阅组为空时移除,并延迟停止直播(直播类型)。
+ // 页面刷新/短暂断网会重建连接,延迟停流可避免视频空档;期间重新订阅会取消停止。
if (subscriberIds.isEmpty()) {
String subscriptionKey = entry.getKey();
if (subscriptionKey.contains(MessageType.LIVESTREAM.getType())) {
String sn = subscriptionKey.split(":")[0];
- _liveStreamService.StopLiveStreaming(sn);
- log.info("设备[{}]无订阅用户,已停止直播流", sn);
+ scheduleStopLiveStream(sn);
}
iterator.remove(); // 安全移除空订阅组
}
}
+ }
+ }
+
+ /**
+ * 延迟停止直播流。
+ * 页面刷新/网络抖动会先断开旧连接再建立新连接,断开时立即停流会让新页面重新起流,
+ * 视频出现空档甚至丢失;这里延迟 LIVE_STOP_DELAY_SECONDS 秒再停,期间有新订阅则取消。
+ */
+ private void scheduleStopLiveStream(String sn) {
+ String subscriptionKey = getSubscriptionKey(sn, MessageType.LIVESTREAM.getType());
+ pendingLiveStops.compute(sn, (key, oldFuture) -> {
+ if (oldFuture != null && !oldFuture.isDone()) {
+ oldFuture.cancel(false);
+ }
+ return liveStopExecutor.schedule(() -> {
+ pendingLiveStops.remove(sn);
+ Set<String> subscriberIds = subscriptions.get(subscriptionKey);
+ // 延迟期间又有用户订阅(如页面刷新重连),不停止直播
+ if (subscriberIds != null && !subscriberIds.isEmpty()) {
+ log.info("设备[{}]延迟停流期间重新订阅,继续直播", sn);
+ return;
+ }
+ _liveStreamService.StopLiveStreaming(sn);
+ log.info("设备[{}]无订阅用户(已延迟{}秒),停止直播流", sn, LIVE_STOP_DELAY_SECONDS);
+ }, LIVE_STOP_DELAY_SECONDS, TimeUnit.SECONDS);
+ });
+ }
+
+ /**
+ * 取消待执行的延迟停流任务(设备重新被订阅时调用)。
+ */
+ private void cancelPendingLiveStop(String sn) {
+ ScheduledFuture<?> future = pendingLiveStops.remove(sn);
+ if (future != null && !future.isDone()) {
+ future.cancel(false);
}
}
@@ -406,6 +460,10 @@
log.error("WebSocket心跳检测线程池关闭被中断", e);
}
}
+ // 关闭延迟停流线程池
+ if (liveStopExecutor != null && !liveStopExecutor.isShutdown()) {
+ liveStopExecutor.shutdownNow();
+ }
log.info("WebSocket处理器资源已销毁");
}
}
\ No newline at end of file
--
Gitblit v1.9.3