diff --git a/web/src/components/player/MsePlayer.tsx b/web/src/components/player/MsePlayer.tsx index 1a2b1b6cbf..e8daed4437 100644 --- a/web/src/components/player/MsePlayer.tsx +++ b/web/src/components/player/MsePlayer.tsx @@ -81,6 +81,7 @@ function MSEPlayer({ const wsRef = useRef(null); const reconnectTIDRef = useRef(null); const intentionalDisconnectRef = useRef(false); + const onCloseRef = useRef<(() => void) | null>(null); const ondataRef = useRef<((data: ArrayBufferLike) => void) | null>(null); const onmessageRef = useRef<{ [key: string]: (msg: { value: string; type: string }) => void; @@ -167,6 +168,8 @@ function MSEPlayer({ wsRef.current = new WebSocket(wsURL); wsRef.current.binaryType = "arraybuffer"; wsRef.current.addEventListener("open", onOpen); + // Capture current onClose identity so removeEventListener can find it later + onCloseRef.current = onClose; wsRef.current.addEventListener("close", onClose); // we know that these deps are correct // eslint-disable-next-line react-hooks/exhaustive-deps @@ -200,20 +203,23 @@ function MSEPlayer({ intentionalDisconnectRef.current = true; setWsState(WebSocket.CLOSED); - // Remove event listeners to prevent them firing during close + // Remove event listeners to prevent them firing during close. + // Use onCloseRef to remove the exact function that was attached in onConnect, + // since onClose may have been recreated by React since then. try { ws.removeEventListener("open", onOpen); - ws.removeEventListener("close", onClose); + if (onCloseRef.current) { + ws.removeEventListener("close", onCloseRef.current); + onCloseRef.current = null; + } } catch { // Ignore errors removing listeners } - // Only call close() if the socket is OPEN or CLOSING - // For CONNECTING or CLOSED sockets, just let it die - if ( - currentReadyState === WebSocket.OPEN || - currentReadyState === WebSocket.CLOSING - ) { + // Close the socket in any non-CLOSED state, including CONNECTING. + // A CONNECTING socket that is not closed will complete its handshake + // and remain open, leaking a browser connection. + if (currentReadyState !== WebSocket.CLOSED) { try { ws.close(); } catch {