diff --git a/src/App.tsx b/src/App.tsx index 2919076..374fc05 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,6 +10,7 @@ import BugReportIcon from "@mui/icons-material/BugReport"; import FormatListNumberedIcon from "@mui/icons-material/FormatListNumbered"; import LinkIcon from "@mui/icons-material/Link"; import PowerSettingsNewIcon from "@mui/icons-material/PowerSettingsNew"; +import SyncIcon from "@mui/icons-material/Sync"; import WifiIcon from "@mui/icons-material/Wifi"; import { Alert, @@ -34,6 +35,7 @@ import { SettingItem } from "./components/ui/SettingItem"; import { useNcmTheme } from "./hooks/useNcmTheme"; import { autoConnectAtom, + autoReconnectAtom, type ConnectionStatus, connectionErrorAtom, connectionStatusAtom, @@ -104,6 +106,7 @@ export default function App() { function Main() { const [wsUrl, setWsUrl] = useAtom(wsUrlAtom); const [autoConnect, setAutoConnect] = useAtom(autoConnectAtom); + const [autoReconnect, setAutoReconnect] = useAtom(autoReconnectAtom); const [status, setStatus] = useAtom(connectionStatusAtom); const [error, setError] = useAtom(connectionErrorAtom); const [displayError, setDisplayError] = useState(error); @@ -265,6 +268,18 @@ function Main() { } /> + } + title="断开自动重连" + description="连接断开后自动尝试重新连接(每3秒重试)" + action={ + setAutoReconnect(checked)} + /> + } + /> + } title="播放进度偏移量" diff --git a/src/components/headless/AmllWsClient.tsx b/src/components/headless/AmllWsClient.tsx index 72f9e99..5c1ec3f 100644 --- a/src/components/headless/AmllWsClient.tsx +++ b/src/components/headless/AmllWsClient.tsx @@ -7,6 +7,7 @@ import { useAtom, useAtomValue, useSetAtom } from "jotai"; import { useCallback, useEffect, useRef } from "react"; import { autoConnectAtom, + autoReconnectAtom, connectionErrorAtom, connectionStatusAtom, lyricAtom, @@ -37,10 +38,15 @@ import { AudioDataBus } from "./InfLinkBridge"; export function AmllWsClient() { const wsRef = useRef(null); const hasAutoConnected = useRef(false); + const hasAttemptedConnect = useRef(false); + const reconnectTimerRef = useRef | null>(null); + const isManualDisconnect = useRef(false); + const autoReconnectRef = useRef(false); const coverManagerRef = useRef(new CoverManager()); const wsUrl = useAtomValue(wsUrlAtom); const autoConnect = useAtomValue(autoConnectAtom); + const autoReconnect = useAtomValue(autoReconnectAtom); const [status, setStatus] = useAtom(connectionStatusAtom); const setError = useSetAtom(connectionErrorAtom); @@ -62,9 +68,44 @@ export function AmllWsClient() { const setRepeatMode = useSetAtom(setRepeatModeAtom); const setShuffleMode = useSetAtom(setShuffleModeAtom); + const clearReconnectTimer = useCallback(() => { + if (reconnectTimerRef.current) { + clearTimeout(reconnectTimerRef.current); + reconnectTimerRef.current = null; + } + }, []); + + const scheduleReconnect = useCallback(() => { + if (!autoReconnectRef.current || isManualDisconnect.current) return; + + clearReconnectTimer(); + + reconnectTimerRef.current = setTimeout(() => { + if (autoReconnectRef.current && !isManualDisconnect.current) { + setStatus("connecting"); + } + }, 3000); + }, [setStatus, clearReconnectTimer]); + + useEffect(() => { + autoReconnectRef.current = autoReconnect; + + if (!autoReconnect) { + clearReconnectTimer(); + } else if ( + hasAttemptedConnect.current && + (status === "disconnected" || status === "error") + ) { + if (!isManualDisconnect.current) { + scheduleReconnect(); + } + } + }, [autoReconnect, status, clearReconnectTimer, scheduleReconnect]); + useEffect(() => { if (!hasAutoConnected.current && autoConnect && status === "disconnected") { hasAutoConnected.current = true; + isManualDisconnect.current = false; setStatus("connecting"); } }, [autoConnect, status, setStatus]); @@ -77,11 +118,15 @@ export function AmllWsClient() { useEffect(() => { if (status === "connecting" && !wsRef.current) { + isManualDisconnect.current = false; + hasAttemptedConnect.current = true; const ws = new WebSocket(wsUrl); wsRef.current = ws; ws.onopen = () => { + clearReconnectTimer(); setStatus("connected"); + setError(""); ws.send(JSON.stringify({ type: "initialize" })); }; @@ -140,6 +185,7 @@ export function AmllWsClient() { if (wsRef.current === ws) { setStatus("disconnected"); wsRef.current = null; + scheduleReconnect(); } }; @@ -148,9 +194,12 @@ export function AmllWsClient() { setStatus("error"); setError("无法连接到 AMLL Player,请检查地址是否正确"); wsRef.current = null; + scheduleReconnect(); } }; } else if (status === "disconnected" && wsRef.current) { + isManualDisconnect.current = true; + clearReconnectTimer(); wsRef.current.close(); wsRef.current = null; } @@ -159,6 +208,8 @@ export function AmllWsClient() { wsUrl, setStatus, setError, + scheduleReconnect, + clearReconnectTimer, play, pause, next, @@ -171,12 +222,13 @@ export function AmllWsClient() { useEffect(() => { return () => { + clearReconnectTimer(); if (wsRef.current) { wsRef.current.close(); wsRef.current = null; } }; - }, []); + }, [clearReconnectTimer]); useEffect(() => { if (!songInfo || status !== "connected") return; diff --git a/src/store/index.ts b/src/store/index.ts index 8f58913..0fdc546 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -46,6 +46,12 @@ export const autoConnectAtom = atomWithStorage( false, ); +/** 是否在连接断开后自动重连 */ +export const autoReconnectAtom = atomWithStorage( + "amll-ws-connector:autoReconnect", + true, +); + /** WebSocket 连接状态 */ export type ConnectionStatus = | "disconnected"