From 9d77536708f86fb5087c0c6149145066ad98153d Mon Sep 17 00:00:00 2001
From: liusuyi <1951119284@qq.com>
Date: Wed, 15 Oct 2025 14:24:10 +0800
Subject: [PATCH] 增加websocket订阅无人机视频

---
 ard-system/src/main/resources/mapper/data/ArdNoFlyZoneMapper.xml                      |    0 
 ard-work/src/main/java/com/ard/work/component/websocket/handler/UavStreamManager.java |  131 +++++++++++++++++++++++++++++++++++++++++++
 ard-work/src/main/java/com/ard/work/component/websocket/handler/TyMessageHandler.java |   42 +++++++++++--
 3 files changed, 166 insertions(+), 7 deletions(-)

diff --git a/ard-system/src/main/resources/mapper/noflyzone/ArdNoFlyZoneMapper.xml b/ard-system/src/main/resources/mapper/data/ArdNoFlyZoneMapper.xml
similarity index 100%
rename from ard-system/src/main/resources/mapper/noflyzone/ArdNoFlyZoneMapper.xml
rename to ard-system/src/main/resources/mapper/data/ArdNoFlyZoneMapper.xml
diff --git a/ard-work/src/main/java/com/ard/work/component/websocket/handler/TyMessageHandler.java b/ard-work/src/main/java/com/ard/work/component/websocket/handler/TyMessageHandler.java
index 58553f5..f6ce332 100644
--- a/ard-work/src/main/java/com/ard/work/component/websocket/handler/TyMessageHandler.java
+++ b/ard-work/src/main/java/com/ard/work/component/websocket/handler/TyMessageHandler.java
@@ -23,6 +23,8 @@
     @Resource
     private UavControlService uavControlService;
     @Resource
+    private UavStreamManager uavStreamManager;
+    @Resource
     private UavStateManager uavStateManager;
     @Resource
     private UavTelemetryManager uavTelemetryManager;
@@ -44,8 +46,8 @@
             BaseMessage<?> baseMessage = JSON.parseObject(payload, BaseMessage.class);
             MessageType messageType = MessageType.fromString(baseMessage.getType());
             switch (messageType) {
-                case STREAM: // 直播地址
-                    
+                case STREAM:
+                    handleStreamMessage(payload, userId);
                     break;
                 case STATE:
                     handleStateMessage(payload, userId);
@@ -65,6 +67,34 @@
             }
         } catch (Exception e) {
             log.error("Error handling message: {}", message, e);
+        }
+    }
+
+    /**
+     * 处理流消息
+     * <p>
+     * 该方法解析给定的负载数据,以获取无人机流消息,并根据消息类型决定是否开始或停止流
+     *
+     * @param payload 消息的负载数据,通常为JSON字符串
+     * @param userId  接收消息的用户ID
+     */
+    private void handleStreamMessage(String payload, Long userId) {
+        // 解析JSON负载数据为BaseMessage对象,包含UavStreamMessage类型的有效载荷
+        BaseMessage<UavStreamMessage> streamMessage = JSON.parseObject(payload,
+                new TypeReference<BaseMessage<UavStreamMessage>>() {
+                });
+        UavStreamMessage uavStreamMessage = streamMessage.getPayload();
+
+        // 检查解析后的UavStreamMessage对象是否非空
+        if (uavStreamMessage != null) {
+            String boxSn = uavStreamMessage.getBoxSn();
+
+            // 根据Subscribe决定操作类型:true表示开始拉流,false表示停止拉流
+            if (uavStreamMessage.getSubscribe()) {
+                uavStreamManager.subscribe(boxSn, userId);
+            } else {
+                uavStreamManager.unsubscribe(boxSn, userId);
+            }
         }
     }
 
@@ -233,11 +263,9 @@
         WebSocketSessionManager.removeSession(userId);
         log.info("【cmd】size: {}", WebSocketSessionManager.getOnlineUserCount());
 
-        if (userId != null) {
-            // 释放该用户所有 UAV 控制权
-            uavControlService.releaseAllControls(userId);
-        }
-
+        // 释放该用户所有 UAV 控制权
+        uavControlService.releaseAllControls(userId);
+        uavStreamManager.stopAllPullStreamByUser(userId);
         uavStateManager.removeSubscribe(userId);
         uavTelemetryManager.removeSubscribe(userId);
         uavSignalManager.removeSubscribe(userId);
diff --git a/ard-work/src/main/java/com/ard/work/component/websocket/handler/UavStreamManager.java b/ard-work/src/main/java/com/ard/work/component/websocket/handler/UavStreamManager.java
new file mode 100644
index 0000000..e24af6f
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/work/component/websocket/handler/UavStreamManager.java
@@ -0,0 +1,131 @@
+package com.ard.work.component.websocket.handler;
+
+import com.alibaba.fastjson2.JSON;
+
+import com.ard.vtdu.domain.ArdVtdu;
+import com.ard.work.component.websocket.utils.WebSocketUtil;
+import com.ard.work.domain.message.BaseMessage;
+import com.ard.work.domain.receiver.BoxStreamUrlReceiver;
+import com.ard.work.service.ITyService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+@Service
+@Slf4j
+public class UavStreamManager {
+
+    private static final String STREAM_LOG_PREFIX = "【stream】";
+    private static final String STREAM_NOT_FOUND_MSG = "Box stream not found: ";
+    private static final String STREAM_CLOSED_MSG = "Stream closed due to no active users: ";
+    private static final String STREAM_IN_USE_MSG = "Stream is still in use, remaining users: ";
+
+    @Resource
+    private ITyService tyService;
+
+    // 盒子拉流地址缓存 key:boxSn value: webrtcUrl
+    public static final ConcurrentMap<String, String> BOX_STREAM_PULL_MAP = new ConcurrentHashMap<>();
+    // 盒子拉流用户集合缓存 key:boxSn value:Set<UserId>
+    public static final ConcurrentMap<String, Set<Long>> STREAM_USER_MAP = new ConcurrentHashMap<>();
+
+    /**
+     * 开始拉流
+     *
+     * @param boxSn  盒子序列号
+     * @param userId 用户ID
+     */
+    public void subscribe(String boxSn, Long userId) {
+
+        STREAM_USER_MAP.computeIfAbsent(boxSn, k -> ConcurrentHashMap.newKeySet()).add(userId);
+        String webrtcUrl = getWebrtcUrl(boxSn);
+        if (Objects.equals(webrtcUrl, "")) {
+            log.warn("{}{}{}", STREAM_LOG_PREFIX, STREAM_NOT_FOUND_MSG, boxSn);
+            BaseMessage<String> baseMessage = new BaseMessage<>("stream", STREAM_NOT_FOUND_MSG + boxSn);
+            sendMessageToUser(userId, JSON.toJSONString(baseMessage));
+        }
+        BOX_STREAM_PULL_MAP.put(boxSn, webrtcUrl);
+        BaseMessage<String> baseMessage = new BaseMessage<>("stream", webrtcUrl);
+        sendMessageToUser(userId, JSON.toJSONString(baseMessage));
+    }
+
+    /**
+     * 停止拉流
+     *
+     * @param boxSn  盒子序列号
+     * @param userId 用户ID
+     */
+    public void unsubscribe(String boxSn, Long userId) {
+        synchronized (this) {
+            Set<Long> users = STREAM_USER_MAP.get(boxSn);
+            if (users == null) {
+                log.warn("{}No active users for box: {}", STREAM_LOG_PREFIX, boxSn);
+                BaseMessage<String> baseMessage = new BaseMessage<>("stream", "No active users for box: " + boxSn);
+                sendMessageToUser(userId, JSON.toJSONString(baseMessage));
+                return;
+            }
+
+            users.remove(userId);
+            String message = "";
+            if (users.isEmpty()) {
+                BOX_STREAM_PULL_MAP.remove(boxSn);
+                STREAM_USER_MAP.remove(boxSn);
+                log.info("{}{}{}", STREAM_LOG_PREFIX, STREAM_CLOSED_MSG, boxSn);
+                message = STREAM_CLOSED_MSG + boxSn;
+            } else {
+                log.info("{}{}{}", STREAM_LOG_PREFIX, STREAM_IN_USE_MSG, users);
+                message = STREAM_IN_USE_MSG + boxSn;
+            }
+            BaseMessage<String> baseMessage = new BaseMessage<>("stream", message);
+            sendMessageToUser(userId, JSON.toJSONString(baseMessage));
+        }
+    }
+
+    /**
+     * 停止拉流
+     *
+     * @param userId 用户ID
+     */
+    public void stopAllPullStreamByUser(Long userId) {
+        synchronized (this) {
+            STREAM_USER_MAP.forEach((boxSn, users) -> unsubscribe(boxSn, userId));
+        }
+    }
+
+    /**
+     * 发送消息给指定用户
+     *
+     * @param userId  用户ID
+     * @param message 消息内容
+     */
+    private void sendMessageToUser(Long userId, String message) {
+        try {
+            WebSocketUtil.sendMessage(userId, message);
+        } catch (Exception e) {
+            log.error("{}Failed to send message to user: {}", STREAM_LOG_PREFIX, userId, e);
+        }
+    }
+
+
+    /**
+     * 通过盒子编号获取webrtc地址
+     *
+     * @param boxSn 盒子编号
+     * @return String webrtcUrl
+     */
+    private String getWebrtcUrl(String boxSn) {
+        try {
+            BoxStreamUrlReceiver boxLiveUrl = tyService.getBoxLiveUrl(boxSn);
+            if (boxLiveUrl != null) {
+                return boxLiveUrl.getRtc();
+            }
+        } catch (Exception e) {
+            log.error("获取媒体地址异常:{}", e.getMessage());
+        }
+        return "";
+    }
+}
\ No newline at end of file

--
Gitblit v1.9.3