package com.ard.tianyu.service.impl; import com.ard.tianyu.model.receiver.BoxStreamUrlReceiver; import com.ard.tianyu.service.ITyService; import com.ard.tianyu.websocket.utils.WebSocketUtil; import com.ard.vtdu.domain.ArdVtdu; import com.ard.vtdu.service.IMediaService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import javax.annotation.Resource; 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 = STREAM_LOG_PREFIX + "Box stream not found: "; private static final String STREAM_CLOSED_MSG = STREAM_LOG_PREFIX + "Stream closed due to no active users: "; private static final String STREAM_IN_USE_MSG = STREAM_LOG_PREFIX + "Stream is still in use, remaining users: "; @Resource private ITyService tyService; @Resource private IMediaService mediaService; // 盒子拉流地址缓存 key:boxSn value: webrtcUrl public static final ConcurrentMap BOX_STREAM_PULL_MAP = new ConcurrentHashMap<>(); // 盒子拉流用户集合缓存 key:boxSn value:Set public static final ConcurrentMap> STREAM_USER_MAP = new ConcurrentHashMap<>(); /** * 开始拉流 * * @param boxSn 盒子序列号 * @param userId 用户ID */ public void startPullStream(String boxSn, String userId) { STREAM_USER_MAP.computeIfAbsent(boxSn, k -> ConcurrentHashMap.newKeySet()).add(userId); String webrtcUrl = BOX_STREAM_PULL_MAP.get(boxSn); if (webrtcUrl != null) { log.info("{}Already pulled stream: {}", STREAM_LOG_PREFIX, webrtcUrl); sendMessageToUser(userId, webrtcUrl); return; } try { BoxStreamUrlReceiver boxLiveUrl = tyService.getBoxLiveUrl(boxSn); if (boxLiveUrl == null) { log.warn("{}{}{}", STREAM_LOG_PREFIX, STREAM_NOT_FOUND_MSG, boxSn); sendMessageToUser(userId, STREAM_NOT_FOUND_MSG + boxSn); return; } ArdVtdu vtdu = ArdVtdu.builder() .name(boxSn) .mode(0) .isCode(0) .rtspSource(boxLiveUrl.getRtsp()) .build(); ArdVtdu ardVtdu = mediaService.addPath(vtdu); webrtcUrl = ardVtdu.getWebrtcUrl(); BOX_STREAM_PULL_MAP.put(boxSn, webrtcUrl); log.info("{}First pulled stream: {}", STREAM_LOG_PREFIX, webrtcUrl); sendMessageToUser(userId, webrtcUrl); } catch (Exception e) { log.error("{}Failed to start pull stream for box: {}", STREAM_LOG_PREFIX, boxSn, e); sendMessageToUser(userId, "Failed to start pull stream due to an internal error."); } } /** * 停止拉流 * * @param boxSn 盒子序列号 * @param userId 用户ID */ public void stopPullStream(String boxSn, String userId) { synchronized (this) { Set users = STREAM_USER_MAP.get(boxSn); if (users == null) { log.warn("{}No active users for box: {}", STREAM_LOG_PREFIX, boxSn); sendMessageToUser(userId, "No active users for box: " + boxSn); return; } users.remove(userId); if (users.isEmpty()) { mediaService.removePath(boxSn); BOX_STREAM_PULL_MAP.remove(boxSn); STREAM_USER_MAP.remove(boxSn); log.info("{}{}{}", STREAM_LOG_PREFIX, STREAM_CLOSED_MSG, boxSn); sendMessageToUser(userId, STREAM_CLOSED_MSG + boxSn); } else { log.info("{}{}{}", STREAM_LOG_PREFIX, STREAM_IN_USE_MSG, users); sendMessageToUser(userId, STREAM_IN_USE_MSG + users); } } } /** * 停止拉流 * * @param userId 用户ID */ public void stopAllPullStreamByUser(String userId) { synchronized (this) { STREAM_USER_MAP.forEach((boxSn, users) -> stopPullStream(boxSn, userId)); } } /** * 发送消息给指定用户 * * @param userId 用户ID * @param message 消息内容 */ private void sendMessageToUser(String userId, String message) { try { WebSocketUtil.sendMessage(userId, message); } catch (Exception e) { log.error("{}Failed to send message to user: {}", STREAM_LOG_PREFIX, userId, e); } } }