import React, { useEffect, useRef, useState } from "react"; import AgoraRTC, { IAgoraRTCClient, IRemoteVideoTrack } from "agora-rtc-sdk-ng"; import { Badge, message } from "antd"; AgoraRTC.setLogLevel(2); // 2=只输出warning/error,屏蔽debug日志 const APP_ID = "7887b4db6033410d9ac3264b40ca9c1a"; const TOKEN = "007eJxTYPh6QSj59sUzD3mPNm64dK9n/n1PP64UzrhzdwUeqMUKv1BVYDC3sDBPMklJMjMwNjYxNEixTEw2NjIzSTIxSE60TDZMlOdYldUQyMiQXKbEwAiFID43g4mhbklGUWpiroExAwMAjGAhHQ=="; export default function AgrtcRoomRobot({ CHANNEL }: { CHANNEL: string }) { const [uid] = useState(() => Number(Math.floor(Math.random() * 100000))); const [connected, setConnected] = useState(false); const [joined, setJoined] = useState(false); const [hasRemoteVideo, setHasRemoteVideo] = useState(false); const remoteVideoRef = useRef(null); const clientRef = useRef(null); const remoteVideoTrackRef = useRef(null); const joiningRef = useRef(false); const reconnectTimerRef = useRef(null); const subscribedUidRef = useRef(null); useEffect(() => { const client = AgoraRTC.createClient({ mode: "live", codec: "h264", }); clientRef.current = client; client.setClientRole("audience"); // ========= 删掉了 setParameters,Web NG不支持!======== let isUnmounted = false; let videoWatchDogTimer: NodeJS.Timeout | null = null; let unpublishTimer: NodeJS.Timeout | null = null; const clearRemoteVideo = () => { if (remoteVideoTrackRef.current) { try { remoteVideoTrackRef.current.stop(); } catch (e) { console.warn("stop track error", e); } remoteVideoTrackRef.current = null; } subscribedUidRef.current = null; setHasRemoteVideo(false); if (videoWatchDogTimer) clearInterval(videoWatchDogTimer); }; const startVideoWatchDog = () => { if (videoWatchDogTimer) clearInterval(videoWatchDogTimer); videoWatchDogTimer = setInterval(async () => { const track = remoteVideoTrackRef.current; const dom = remoteVideoRef.current; if (!track || !dom) return; const videoEl = dom.querySelector("video") as HTMLVideoElement; if (!videoEl) return; if (videoEl.readyState < 2) { console.warn("看门狗检测视频无画面,尝试重新play"); try { await track.play(dom); } catch (e) { console.warn("看门狗play异常", e); } } }, 3000); }; const playRemoteTrack = async (track: IRemoteVideoTrack) => { const dom = remoteVideoRef.current; if (!dom || !track) return; try { await track.play(dom); console.log("远端视频播放成功"); setHasRemoteVideo(true); startVideoWatchDog(); } catch (err: any) { if (err.name === "AbortError") { console.warn("播放请求被中断 AbortError,忽略", err); return; } console.error("视频播放失败", err); message.error("视频播放失败"); } }; 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 && subscribedUidRef.current !== remoteUser.uid) { await client.subscribe(remoteUser, "video"); remoteVideoTrackRef.current = remoteUser.videoTrack; subscribedUidRef.current = remoteUser.uid; await playRemoteTrack(remoteUser.videoTrack!); } } } 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); if (mediaType !== "video") return; if (subscribedUidRef.current === user.uid) { console.log("该用户已订阅,跳过"); return; } // 取消待执行的清空定时器 if (unpublishTimer) clearTimeout(unpublishTimer); await client.subscribe(user, mediaType); remoteVideoTrackRef.current = user.videoTrack; subscribedUidRef.current = user.uid; await playRemoteTrack(user.videoTrack); }; const onUserUnpublished = (user: any, mediaType: "video" | "audio") => { if (isUnmounted) return; console.log("user-unpublished", user.uid, mediaType, subscribedUidRef.current); if (mediaType === "video" && subscribedUidRef.current === user.uid) { if (unpublishTimer) clearTimeout(unpublishTimer); // 延迟2s清空,短闪断自动恢复不黑屏 unpublishTimer = setTimeout(() => { clearRemoteVideo(); }, 2000); } }; const onUserLeft = (user: any) => { if (isUnmounted) return; console.log("user-left", user.uid); if (subscribedUidRef.current === user.uid) { if (unpublishTimer) clearTimeout(unpublishTimer); clearRemoteVideo(); } }; const onUserJoined = (user: any) => { if (!isUnmounted) console.log("远端用户进入频道:", user.uid); }; const onTokenWillExpire = () => { console.warn("token即将过期,请更新token"); message.warning("Token即将过期,视频可能很快断开"); }; 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 && subscribedUidRef.current !== remoteUser.uid) { await client.subscribe(remoteUser, "video"); remoteVideoTrackRef.current = remoteUser.videoTrack; subscribedUidRef.current = remoteUser.uid; await playRemoteTrack(remoteUser.videoTrack!); } } } catch (err: any) { if (err?.code === "OPERATION_ABORTED") { console.log("join操作被取消,忽略"); 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); if (unpublishTimer) clearTimeout(unpublishTimer); if (videoWatchDogTimer) clearInterval(videoWatchDogTimer); 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; }; }, [CHANNEL]); return (
频道:{CHANNEL}
{!hasRemoteVideo && (
视频加载中,等待远端推流...
)}
); }