diff --git a/client/src/MediaControl.css b/client/src/MediaControl.css index 835f95c..1e4c46d 100644 --- a/client/src/MediaControl.css +++ b/client/src/MediaControl.css @@ -27,7 +27,7 @@ .MediaControl { display: flex; - position: fixed; + position: absolute; flex-direction: row; justify-content: flex-end; align-items: center; diff --git a/client/src/MediaControl.tsx b/client/src/MediaControl.tsx index 5e52c74..b1b139a 100644 --- a/client/src/MediaControl.tsx +++ b/client/src/MediaControl.tsx @@ -10,6 +10,8 @@ import Box from "@mui/material/Box"; import useWebSocket, { ReadyState } from "react-use-websocket"; import { Session } from "./GlobalContext"; import WebRTCStatus from "./WebRTCStatus"; +import Moveable from "react-moveable"; +import { flushSync } from "react-dom"; const debug = true; // When true, do not send host candidates to the signaling server. Keeps TURN relays preferred. @@ -559,20 +561,20 @@ const MediaAgent = (props: MediaAgentProps) => { if (connection.connectionState === "failed") { console.error(`media-agent - addPeer:${peer.peer_name} Connection failed for`, peer.peer_name); - + // Immediate cleanup of failed connection connectionsRef.current.delete(peer_id); makingOfferRef.current.delete(peer_id); isNegotiatingRef.current.delete(peer_id); initiatedOfferRef.current.delete(peer_id); - + // Clean up the peer from state setPeers((prevPeers) => { const updated = { ...prevPeers }; delete updated[peer_id]; return updated; }); - + // Close the connection try { connection.close(); @@ -580,8 +582,12 @@ const MediaAgent = (props: MediaAgentProps) => { console.warn(`media-agent - Error closing failed connection:`, e); } } else if (connection.connectionState === "disconnected") { - console.warn(`media-agent - addPeer:${peer.peer_name} Connection disconnected for`, peer.peer_name, "- may recover"); - + console.warn( + `media-agent - addPeer:${peer.peer_name} Connection disconnected for`, + peer.peer_name, + "- may recover" + ); + // Set a timeout for disconnected state recovery setTimeout(() => { if (connection.connectionState === "disconnected" || connection.connectionState === "failed") { @@ -759,7 +765,7 @@ const MediaAgent = (props: MediaAgentProps) => { label: t.label, id: t.id, }); - + // Enable tracks for bots that need audio/video input (whisper, synthetic media, etc.) if (peer.peer_name.includes("-bot")) { if (t.kind === "audio" || t.kind === "video") { @@ -767,7 +773,7 @@ const MediaAgent = (props: MediaAgentProps) => { console.log(`media-agent - addPeer:${peer.peer_name} Force enabled ${t.kind} track for bot`); } } - + connection.addTrack(t, media); }); } else if (!localUserHasMedia) { @@ -809,7 +815,7 @@ const MediaAgent = (props: MediaAgentProps) => { } } }, - [peers, setPeers, media, sendJsonMessage, localUserHasMedia, updatePeerConnectionState] + [peers, setPeers, media, sendJsonMessage, updatePeerConnectionState, session?.has_media] ); // Process queued peers when media becomes available @@ -893,10 +899,7 @@ const MediaAgent = (props: MediaAgentProps) => { const candidateInit: RTCIceCandidateInit = { candidate: candStr ?? "", sdpMid: candidate.sdpMid ?? undefined, - sdpMLineIndex: - typeof candidate.sdpMLineIndex === "number" - ? candidate.sdpMLineIndex - : undefined, + sdpMLineIndex: typeof candidate.sdpMLineIndex === "number" ? candidate.sdpMLineIndex : undefined, }; try { @@ -1300,6 +1303,26 @@ const MediaControl: React.FC = ({ isSelf, peer, className }) const [muted, setMuted] = useState(peer?.muted || false); const [videoOn, setVideoOn] = useState(peer?.video_on !== false); const [isValid, setIsValid] = useState(false); + const [frame, setFrame] = useState<{ translate: [number, number] }>({ translate: [0, 0] }); + const targetRef = useRef(null); + const moveableRef = useRef(null); + + // Initialize position based on peer session_id to avoid stacking + useEffect(() => { + if (!peer) return; + + // Create a simple hash of the session_id to get a position offset + let hash = 0; + for (let i = 0; i < peer.session_id.length; i++) { + hash = (hash << 5) - hash + peer.session_id.charCodeAt(i); + hash = hash & hash; // Convert to 32-bit integer + } + + const x = (Math.abs(hash) % 300) + 50; // Random x between 50-350px + const y = (Math.abs(hash) % 200) + 50; // Random y between 50-250px + + setFrame({ translate: [x, y] }); + }, [peer]); useEffect(() => { if (!peer) return; @@ -1394,14 +1417,21 @@ const MediaControl: React.FC = ({ isSelf, peer, className }) }); }, [muted, peer]); + // Debug target element useEffect(() => { - if (!peer || peer.dead || !peer.attributes?.srcObject) return; - - const stream = peer.attributes.srcObject as MediaStream; - stream.getVideoTracks().forEach((t) => { - t.enabled = videoOn; - }); - }, [videoOn, peer]); + console.log("Target ref current:", targetRef.current, "for peer:", peer?.session_id); + if (targetRef.current) { + console.log("Target element rect:", targetRef.current.getBoundingClientRect()); + console.log("Target element computed style:", { + position: getComputedStyle(targetRef.current).position, + left: getComputedStyle(targetRef.current).left, + top: getComputedStyle(targetRef.current).top, + transform: getComputedStyle(targetRef.current).transform, + width: getComputedStyle(targetRef.current).width, + height: getComputedStyle(targetRef.current).height + }); + } + }, [peer?.session_id]); const toggleMute = (e: React.MouseEvent | React.TouchEvent) => { e.stopPropagation(); @@ -1427,7 +1457,16 @@ const MediaControl: React.FC = ({ isSelf, peer, className }) return (
-
+
{isSelf ? (
@@ -1463,36 +1502,53 @@ const MediaControl: React.FC = ({ isSelf, peer, className }) )} - {/* e.set(frame.translate)} - onDrag={(e) => { - if (Array.isArray(e.beforeTranslate) && e.beforeTranslate.length === 2) { - frame.translate = [e.beforeTranslate[0], e.beforeTranslate[1]]; - } - }} - onResizeStart={(e) => { - e.setOrigin(["%", "%"]); - e.dragStart && e.dragStart.set(frame.translate); - }} - onResize={(e) => { - const { translate } = frame; - e.target.style.width = `${e.width}px`; - e.target.style.height = `${e.height}px`; - e.target.style.transform = `translate(${translate[0]}px, ${translate[1]}px)`; - }} - onRender={(e) => { - const { translate } = frame; - e.target.style.transform = `translate(${translate[0]}px, ${translate[1]}px)`; - }} - /> */} + { + console.log("Moveable drag start", { target: targetRef.current, event: e }); + }} + onDrag={(e) => { + console.log("Moveable drag", { beforeTranslate: e.beforeTranslate, transform: e.transform }); + // Apply the transform directly to the target element + if (targetRef.current) { + targetRef.current.style.transform = e.transform; + } + }} + onDragEnd={(e) => { + console.log("Moveable drag end", { target: targetRef.current }); + // Get the final transform from the element + if (targetRef.current) { + const computedStyle = getComputedStyle(targetRef.current); + const transform = computedStyle.transform; + console.log("Final computed transform:", transform); + + // Parse the transform matrix to get translate values + if (transform && transform !== "none") { + const matrix = new DOMMatrix(transform); + console.log("Parsed matrix translate:", [matrix.m41, matrix.m42]); + setFrame({ translate: [matrix.m41, matrix.m42] }); + } else { + // If no transform, reset to 0,0 + setFrame({ translate: [0, 0] }); + } + } + }} + onResizeStart={(e) => { + e.setOrigin(["%", "%"]); + }} + onResize={(e) => { + e.target.style.width = `${e.width}px`; + e.target.style.height = `${e.height}px`; + }} + />
);