声网组件接入 优化刷新之后路线的显示 兼容上位机最后一次经纬度 的 数据
This commit is contained in:
217
src/components/AgrtcRoomRobot.tsx
Normal file
217
src/components/AgrtcRoomRobot.tsx
Normal file
@@ -0,0 +1,217 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import AgoraRTC, { IAgoraRTCClient, IRemoteVideoTrack } from "agora-rtc-sdk-ng";
|
||||
import { Card, Badge, message, Space } from "antd";
|
||||
|
||||
const APP_ID = "875f6494d87548e783fefbf33f598c97";
|
||||
const TOKEN = "007eJxTYIjPuZr09PC0znd6+z0MMyL6ZHb68OetXW+kO1vHU2ZNtYECg4W5aZqZiaVJCpBhYpFqbmGclpqWlGZsnGZqaZFsaT6pZH5WQyAjA7e+LysjAwSC+JwMxmYlqcUlugZGDAwAbuYeJg==";
|
||||
|
||||
export default function AgrtcRoomRobot({ CHANNEL }) {
|
||||
const [uid] = useState(() => Number(Math.floor(Math.random() * 100000)));
|
||||
const [connected, setConnected] = useState(false);
|
||||
const [joined, setJoined] = useState(false);
|
||||
|
||||
const remoteVideoRef = useRef<HTMLDivElement>(null);
|
||||
const clientRef = useRef<IAgoraRTCClient | null>(null);
|
||||
const remoteVideoTrackRef = useRef<IRemoteVideoTrack | null>(null);
|
||||
|
||||
const joiningRef = useRef(false);
|
||||
const reconnectTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const client = AgoraRTC.createClient({
|
||||
mode: "live",
|
||||
codec: "h264",
|
||||
});
|
||||
clientRef.current = client;
|
||||
client.setClientRole("audience");
|
||||
|
||||
let isUnmounted = false;
|
||||
|
||||
// 停止并清空远端视频
|
||||
const clearRemoteVideo = () => {
|
||||
if (remoteVideoTrackRef.current) {
|
||||
try {
|
||||
remoteVideoTrackRef.current.stop();
|
||||
} catch (e) { /* */ }
|
||||
remoteVideoTrackRef.current = null;
|
||||
}
|
||||
if (remoteVideoRef.current) {
|
||||
remoteVideoRef.current.innerHTML = "";
|
||||
}
|
||||
};
|
||||
|
||||
// 重连逻辑
|
||||
const doReconnect = async () => {
|
||||
if (isUnmounted || joiningRef.current) return;
|
||||
console.log("执行RTC重连");
|
||||
clearRemoteVideo();
|
||||
setJoined(false);
|
||||
|
||||
try {
|
||||
joiningRef.current = true;
|
||||
await client.join(APP_ID, CHANNEL, TOKEN, uid);
|
||||
if (isUnmounted) return;
|
||||
setJoined(true);
|
||||
message.info("RTC重连成功");
|
||||
|
||||
// 重连成功后重新订阅已经在线的上位机
|
||||
for (const remoteUser of client.remoteUsers) {
|
||||
if (isUnmounted) break;
|
||||
if (remoteUser.hasVideo) {
|
||||
await client.subscribe(remoteUser, "video");
|
||||
remoteVideoTrackRef.current = remoteUser.videoTrack;
|
||||
remoteUser.videoTrack?.play(remoteVideoRef.current!);
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
if (err?.code !== "OPERATION_ABORTED") {
|
||||
console.error("重连失败", err);
|
||||
}
|
||||
} finally {
|
||||
joiningRef.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 连接状态变化
|
||||
const onConnStateChange = (curState: string, prevState: string) => {
|
||||
if (isUnmounted) return;
|
||||
console.log("连接状态变更", prevState, "→", curState);
|
||||
setConnected(curState === "CONNECTED");
|
||||
|
||||
// 信令断开,延迟重连
|
||||
if (curState === "DISCONNECTED") {
|
||||
clearRemoteVideo();
|
||||
if (reconnectTimerRef.current) clearTimeout(reconnectTimerRef.current);
|
||||
reconnectTimerRef.current = setTimeout(() => {
|
||||
doReconnect();
|
||||
}, 2000);
|
||||
}
|
||||
};
|
||||
|
||||
// 远端用户发布流
|
||||
const onUserPublished = async (user: any, mediaType: "video" | "audio") => {
|
||||
if (isUnmounted) return;
|
||||
console.log("user-published", user.uid, mediaType);
|
||||
await client.subscribe(user, mediaType);
|
||||
|
||||
if (mediaType === "video" && remoteVideoRef.current) {
|
||||
clearRemoteVideo();
|
||||
remoteVideoTrackRef.current = user.videoTrack;
|
||||
user.videoTrack?.play(remoteVideoRef.current);
|
||||
}
|
||||
};
|
||||
|
||||
// 远端停止发布流(上位机停止推流)
|
||||
const onUserUnpublished = (user: any, mediaType: "video" | "audio") => {
|
||||
if (isUnmounted) return;
|
||||
console.log("user-unpublished", user.uid, mediaType);
|
||||
if (mediaType === "video") {
|
||||
clearRemoteVideo();
|
||||
}
|
||||
};
|
||||
|
||||
// 远端用户离开频道
|
||||
const onUserLeft = (user: any) => {
|
||||
if (isUnmounted) return;
|
||||
console.log("user-left", user.uid);
|
||||
clearRemoteVideo();
|
||||
};
|
||||
|
||||
// 远端用户进入
|
||||
const onUserJoined = (user: any) => {
|
||||
if (!isUnmounted) console.log("远端用户进入频道:", user.uid);
|
||||
};
|
||||
|
||||
// Token即将过期提醒(测试Token有效期有限)
|
||||
const onTokenWillExpire = () => {
|
||||
console.warn("token即将过期,请更新token");
|
||||
message.warning("Token即将过期,视频可能很快断开");
|
||||
// 正式环境:请求后端拿新token client.renewToken(newToken)
|
||||
};
|
||||
|
||||
// 注册事件,保存回调引用用于off解绑
|
||||
client.on("connection-state-change", onConnStateChange);
|
||||
client.on("user-published", onUserPublished);
|
||||
client.on("user-unpublished", onUserUnpublished);
|
||||
client.on("user-left", onUserLeft);
|
||||
client.on("user-joined", onUserJoined);
|
||||
client.on("token-privilege-will-expire", onTokenWillExpire);
|
||||
|
||||
const init = async () => {
|
||||
if (joiningRef.current) return;
|
||||
joiningRef.current = true;
|
||||
try {
|
||||
await client.join(APP_ID, CHANNEL, TOKEN, uid);
|
||||
if (isUnmounted) {
|
||||
console.log("组件已卸载,丢弃join结果");
|
||||
return;
|
||||
}
|
||||
console.log("加入频道成功");
|
||||
setJoined(true);
|
||||
message.success("RTC已加入频道");
|
||||
|
||||
// 上位机已经提前推流,手动订阅
|
||||
for (const remoteUser of client.remoteUsers) {
|
||||
if (isUnmounted) break;
|
||||
console.log("发现已有远端用户:", remoteUser.uid);
|
||||
if (remoteUser.hasVideo) {
|
||||
await client.subscribe(remoteUser, "video");
|
||||
remoteVideoTrackRef.current = remoteUser.videoTrack;
|
||||
remoteUser.videoTrack?.play(remoteVideoRef.current!);
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
if (err?.code === "OPERATION_ABORTED") {
|
||||
console.log("join操作被取消(StrictMode/组件卸载),忽略");
|
||||
return;
|
||||
}
|
||||
console.error("加入频道失败:", err);
|
||||
if (!isUnmounted) {
|
||||
message.error("加入频道失败,请检查appid/token/channel");
|
||||
}
|
||||
} finally {
|
||||
joiningRef.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
init();
|
||||
|
||||
// 组件卸载清理
|
||||
return () => {
|
||||
isUnmounted = true;
|
||||
if (reconnectTimerRef.current) clearTimeout(reconnectTimerRef.current);
|
||||
clearRemoteVideo();
|
||||
|
||||
const c = clientRef.current;
|
||||
if (!c) return;
|
||||
|
||||
c.off("connection-state-change", onConnStateChange);
|
||||
c.off("user-published", onUserPublished);
|
||||
c.off("user-unpublished", onUserUnpublished);
|
||||
c.off("user-left", onUserLeft);
|
||||
c.off("user-joined", onUserJoined);
|
||||
c.off("token-privilege-will-expire", onTokenWillExpire);
|
||||
|
||||
c.leave();
|
||||
clientRef.current = null;
|
||||
};
|
||||
}, [uid]);
|
||||
|
||||
return (
|
||||
<Card title="Agora RTC 上位机视频观看" style={{ width: 1000, margin: "20px auto" }}>
|
||||
<Space direction="vertical" style={{ width: "100%" }}>
|
||||
<div>当前 UID:{uid}</div>
|
||||
<Badge color={connected ? "green" : "red"} text={connected ? "RTC 已连接" : "RTC 未连接"} />
|
||||
<Badge color={joined ? "green" : "orange"} text={joined ? "已加入频道" : "未加入频道"} />
|
||||
|
||||
<div style={{ marginTop: 20 }}>
|
||||
<h3>远端视频(上位机画面)</h3>
|
||||
<div
|
||||
ref={remoteVideoRef}
|
||||
style={{ width: 800, height: 600, background: "#000", borderRadius: 8, overflow: "hidden" }}
|
||||
/>
|
||||
</div>
|
||||
</Space>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -564,8 +564,8 @@ const CesiumMap = React.forwardRef(({ ifClear, ifClearPlanLine, ifClearNavLine,
|
||||
tooltip.innerHTML = `
|
||||
<div style="font-weight:600;margin-bottom:6px;">🤖 ${robot.deviceAlias || robot.serialNumber || robot.name}</div>
|
||||
${deviceValuesHTML ? `<div style="margin-top:8px;"><strong>${t('devices.deviceData')}:</strong>${deviceValuesHTML}</div>` : ''}
|
||||
<div>${t('systemSetting.longitude')}:${Number(robot.lastRunningStatus?.longitude).toFixed(6)}</div>
|
||||
<div>${t('systemSetting.latitude')}:${Number(robot.lastRunningStatus?.latitude).toFixed(6)}</div>
|
||||
<div>${t('systemSetting.longitude')}:${Number(robot?.locationMessage?.longitude || robot?.lastRunningStatus?.longitude).toFixed(6)}</div>
|
||||
<div>${t('systemSetting.latitude')}:${Number(robot?.locationMessage?.latitude || robot?.lastRunningStatus?.latitude).toFixed(6)}</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -1403,7 +1403,7 @@ const CesiumMap = React.forwardRef(({ ifClear, ifClearPlanLine, ifClearNavLine,
|
||||
|
||||
devices.forEach((device, index) => {
|
||||
|
||||
const raw = device?.lastRunningStatus || {};
|
||||
const raw = device?.locationMessage || device?.lastRunningStatus || {};
|
||||
const parsed = parseLngLat(raw.longitude, raw.latitude);
|
||||
if (!parsed) return;
|
||||
|
||||
|
||||
@@ -50,8 +50,15 @@ interface Robot {
|
||||
lastRunningStatus?: {
|
||||
latitude: string | number;
|
||||
longitude: string | number;
|
||||
battery: number;
|
||||
battery: string | number;
|
||||
};
|
||||
locationMessage?: {
|
||||
latitude: string | number;
|
||||
longitude: string | number;
|
||||
battery: string | number;
|
||||
|
||||
};
|
||||
|
||||
drone_callsign?: string;
|
||||
serialNumber?: string;
|
||||
}
|
||||
@@ -1040,8 +1047,8 @@ const RobotControlCenter = ({ setView }) => {
|
||||
};
|
||||
|
||||
const mapFocus = selectedDevice ? {
|
||||
lat: selectedDevice.lastRunningStatus.latitude,
|
||||
lon: selectedDevice.lastRunningStatus.longitude,
|
||||
lat: selectedDevice?.locationMessage?.latitude || selectedDevice?.lastRunningStatus?.latitude,
|
||||
lon: selectedDevice?.locationMessage?.longitude || selectedDevice?.lastRunningStatus?.longitude,
|
||||
height: 500
|
||||
} : { lat: '31.030129089704428', lon: '120.76671769055027', height: 800 };
|
||||
//手柄数据
|
||||
|
||||
@@ -306,7 +306,7 @@ export default function RobotTaskPage() {
|
||||
const runningTask = taskPoolList.find(item => ['EXECUTING', 'PAUSE'].includes(item.taskStaus));
|
||||
|
||||
if (!runningTask) return;
|
||||
fetchRouteDetail(runningTask?.id);
|
||||
//fetchRouteDetail(runningTask?.id);
|
||||
|
||||
// 找到对应的设备
|
||||
const targetDevice = devices.find(d => d.serialNumber === runningTask.deviceId);
|
||||
@@ -510,6 +510,8 @@ export default function RobotTaskPage() {
|
||||
if (!runningPoolTask?.id) return;
|
||||
|
||||
loadTaskPath(runningPoolTask.id);
|
||||
fetchRouteDetail(runningPoolTask?.id);
|
||||
|
||||
|
||||
// 只有当任务是执行状态,并且没有连接的设备时,才自动连接
|
||||
if ((runningPoolTask.taskStaus == 'EXECUTING' || runningPoolTask.taskStaus == 'PAUSE') && !executingRobot) {
|
||||
|
||||
Reference in New Issue
Block a user