Fixing Moveable

This commit is contained in:
James Ketr 2025-09-14 12:54:03 -07:00
parent 3d5f63aa0a
commit ac1ca4ec8f
2 changed files with 107 additions and 51 deletions

View File

@ -27,7 +27,7 @@
.MediaControl { .MediaControl {
display: flex; display: flex;
position: fixed; position: absolute;
flex-direction: row; flex-direction: row;
justify-content: flex-end; justify-content: flex-end;
align-items: center; align-items: center;

View File

@ -10,6 +10,8 @@ import Box from "@mui/material/Box";
import useWebSocket, { ReadyState } from "react-use-websocket"; import useWebSocket, { ReadyState } from "react-use-websocket";
import { Session } from "./GlobalContext"; import { Session } from "./GlobalContext";
import WebRTCStatus from "./WebRTCStatus"; import WebRTCStatus from "./WebRTCStatus";
import Moveable from "react-moveable";
import { flushSync } from "react-dom";
const debug = true; const debug = true;
// When true, do not send host candidates to the signaling server. Keeps TURN relays preferred. // 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") { if (connection.connectionState === "failed") {
console.error(`media-agent - addPeer:${peer.peer_name} Connection failed for`, peer.peer_name); console.error(`media-agent - addPeer:${peer.peer_name} Connection failed for`, peer.peer_name);
// Immediate cleanup of failed connection // Immediate cleanup of failed connection
connectionsRef.current.delete(peer_id); connectionsRef.current.delete(peer_id);
makingOfferRef.current.delete(peer_id); makingOfferRef.current.delete(peer_id);
isNegotiatingRef.current.delete(peer_id); isNegotiatingRef.current.delete(peer_id);
initiatedOfferRef.current.delete(peer_id); initiatedOfferRef.current.delete(peer_id);
// Clean up the peer from state // Clean up the peer from state
setPeers((prevPeers) => { setPeers((prevPeers) => {
const updated = { ...prevPeers }; const updated = { ...prevPeers };
delete updated[peer_id]; delete updated[peer_id];
return updated; return updated;
}); });
// Close the connection // Close the connection
try { try {
connection.close(); connection.close();
@ -580,8 +582,12 @@ const MediaAgent = (props: MediaAgentProps) => {
console.warn(`media-agent - Error closing failed connection:`, e); console.warn(`media-agent - Error closing failed connection:`, e);
} }
} else if (connection.connectionState === "disconnected") { } 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 // Set a timeout for disconnected state recovery
setTimeout(() => { setTimeout(() => {
if (connection.connectionState === "disconnected" || connection.connectionState === "failed") { if (connection.connectionState === "disconnected" || connection.connectionState === "failed") {
@ -759,7 +765,7 @@ const MediaAgent = (props: MediaAgentProps) => {
label: t.label, label: t.label,
id: t.id, id: t.id,
}); });
// Enable tracks for bots that need audio/video input (whisper, synthetic media, etc.) // Enable tracks for bots that need audio/video input (whisper, synthetic media, etc.)
if (peer.peer_name.includes("-bot")) { if (peer.peer_name.includes("-bot")) {
if (t.kind === "audio" || t.kind === "video") { 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`); console.log(`media-agent - addPeer:${peer.peer_name} Force enabled ${t.kind} track for bot`);
} }
} }
connection.addTrack(t, media); connection.addTrack(t, media);
}); });
} else if (!localUserHasMedia) { } 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 // Process queued peers when media becomes available
@ -893,10 +899,7 @@ const MediaAgent = (props: MediaAgentProps) => {
const candidateInit: RTCIceCandidateInit = { const candidateInit: RTCIceCandidateInit = {
candidate: candStr ?? "", candidate: candStr ?? "",
sdpMid: candidate.sdpMid ?? undefined, sdpMid: candidate.sdpMid ?? undefined,
sdpMLineIndex: sdpMLineIndex: typeof candidate.sdpMLineIndex === "number" ? candidate.sdpMLineIndex : undefined,
typeof candidate.sdpMLineIndex === "number"
? candidate.sdpMLineIndex
: undefined,
}; };
try { try {
@ -1300,6 +1303,26 @@ const MediaControl: React.FC<MediaControlProps> = ({ isSelf, peer, className })
const [muted, setMuted] = useState<boolean>(peer?.muted || false); const [muted, setMuted] = useState<boolean>(peer?.muted || false);
const [videoOn, setVideoOn] = useState<boolean>(peer?.video_on !== false); const [videoOn, setVideoOn] = useState<boolean>(peer?.video_on !== false);
const [isValid, setIsValid] = useState<boolean>(false); const [isValid, setIsValid] = useState<boolean>(false);
const [frame, setFrame] = useState<{ translate: [number, number] }>({ translate: [0, 0] });
const targetRef = useRef<HTMLDivElement>(null);
const moveableRef = useRef<any>(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(() => { useEffect(() => {
if (!peer) return; if (!peer) return;
@ -1394,14 +1417,21 @@ const MediaControl: React.FC<MediaControlProps> = ({ isSelf, peer, className })
}); });
}, [muted, peer]); }, [muted, peer]);
// Debug target element
useEffect(() => { useEffect(() => {
if (!peer || peer.dead || !peer.attributes?.srcObject) return; console.log("Target ref current:", targetRef.current, "for peer:", peer?.session_id);
if (targetRef.current) {
const stream = peer.attributes.srcObject as MediaStream; console.log("Target element rect:", targetRef.current.getBoundingClientRect());
stream.getVideoTracks().forEach((t) => { console.log("Target element computed style:", {
t.enabled = videoOn; position: getComputedStyle(targetRef.current).position,
}); left: getComputedStyle(targetRef.current).left,
}, [videoOn, peer]); 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) => { const toggleMute = (e: React.MouseEvent | React.TouchEvent) => {
e.stopPropagation(); e.stopPropagation();
@ -1427,7 +1457,16 @@ const MediaControl: React.FC<MediaControlProps> = ({ isSelf, peer, className })
return ( return (
<Box sx={{ display: "flex", flexDirection: "column", alignItems: "center", minWidth: "200px", minHeight: "100px" }}> <Box sx={{ display: "flex", flexDirection: "column", alignItems: "center", minWidth: "200px", minHeight: "100px" }}>
<div className={`MediaControlSpacer ${className}`} /> <div className={`MediaControlSpacer ${className}`} />
<div className={`MediaControl ${className}`} data-peer={peer.session_id}> <div
ref={targetRef}
className={`MediaControl ${className}`}
data-peer={peer.session_id}
style={{
left: '0px',
top: '0px',
transform: `translate(${frame.translate[0]}px, ${frame.translate[1]}px)`
}}
>
<div className="Controls"> <div className="Controls">
{isSelf ? ( {isSelf ? (
<div onTouchStart={toggleMute} onClick={toggleMute}> <div onTouchStart={toggleMute} onClick={toggleMute}>
@ -1463,36 +1502,53 @@ const MediaControl: React.FC<MediaControlProps> = ({ isSelf, peer, className })
<WebRTCStatus isNegotiating={peer.isNegotiating || false} connectionState={peer.connectionState} /> <WebRTCStatus isNegotiating={peer.isNegotiating || false} connectionState={peer.connectionState} />
</Box> </Box>
)} )}
{/* <Moveable <Moveable
flushSync={flushSync} ref={moveableRef}
pinchable flushSync={flushSync}
draggable pinchable
target={document.querySelector(`.MediaControl[data-peer="${peer.session_id}"]`) as HTMLElement} draggable
resizable target={targetRef.current}
keepRatio resizable
hideDefaultLines={false} keepRatio
edge hideDefaultLines={false}
onDragStart={(e) => e.set(frame.translate)} edge
onDrag={(e) => { onDragStart={(e) => {
if (Array.isArray(e.beforeTranslate) && e.beforeTranslate.length === 2) { console.log("Moveable drag start", { target: targetRef.current, event: e });
frame.translate = [e.beforeTranslate[0], e.beforeTranslate[1]]; }}
} onDrag={(e) => {
}} console.log("Moveable drag", { beforeTranslate: e.beforeTranslate, transform: e.transform });
onResizeStart={(e) => { // Apply the transform directly to the target element
e.setOrigin(["%", "%"]); if (targetRef.current) {
e.dragStart && e.dragStart.set(frame.translate); targetRef.current.style.transform = e.transform;
}} }
onResize={(e) => { }}
const { translate } = frame; onDragEnd={(e) => {
e.target.style.width = `${e.width}px`; console.log("Moveable drag end", { target: targetRef.current });
e.target.style.height = `${e.height}px`; // Get the final transform from the element
e.target.style.transform = `translate(${translate[0]}px, ${translate[1]}px)`; if (targetRef.current) {
}} const computedStyle = getComputedStyle(targetRef.current);
onRender={(e) => { const transform = computedStyle.transform;
const { translate } = frame; console.log("Final computed transform:", transform);
e.target.style.transform = `translate(${translate[0]}px, ${translate[1]}px)`;
}} // 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`;
}}
/>
</div> </div>
</Box> </Box>
); );