tam
This commit is contained in:
@@ -22,73 +22,107 @@ export const ExamGridProctor: React.FC<ExamGridProctorProps> = ({
|
||||
const [isFullscreen, setIsFullscreen] = useState<boolean>(false);
|
||||
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
const subscribedRef = useRef<Set<number>>(new Set());
|
||||
const gridContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const studentIdsString = useMemo(
|
||||
() => students.map((s) => s.studentRkId).join(','),
|
||||
[students]
|
||||
);
|
||||
const onlineKey = useMemo(() => onlineIds.join(','), [onlineIds]);
|
||||
|
||||
const syncSubscriptions = (ws: WebSocket) => {
|
||||
if (ws.readyState !== WebSocket.OPEN) return;
|
||||
const target = new Set(onlineIds);
|
||||
const prev = subscribedRef.current;
|
||||
|
||||
prev.forEach((id) => {
|
||||
if (!target.has(id)) {
|
||||
ws.send(JSON.stringify({ event: 'teacher:unsubscribe', data: { studentId: id } }));
|
||||
prev.delete(id);
|
||||
}
|
||||
});
|
||||
onlineIds.forEach((id) => {
|
||||
if (!prev.has(id)) {
|
||||
ws.send(JSON.stringify({ event: 'teacher:subscribe', data: { studentId: id } }));
|
||||
prev.add(id);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (students.length === 0) return;
|
||||
|
||||
const wsUrl = getWsUrl('/ws?role=teacher');
|
||||
const ws = new WebSocket(wsUrl);
|
||||
wsRef.current = ws;
|
||||
let closed = false;
|
||||
let retryTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
ws.onopen = () => {
|
||||
students.forEach((s) => {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
event: 'teacher:subscribe',
|
||||
data: { studentId: s.studentRkId },
|
||||
})
|
||||
);
|
||||
});
|
||||
};
|
||||
const connect = () => {
|
||||
if (closed) return;
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
try {
|
||||
const msg = JSON.parse(event.data);
|
||||
if (msg.event === 'teacher:screenshot-stream-frame') {
|
||||
const { studentId, imageBuffer } = msg.data;
|
||||
setScreenFrames((prev) => ({ ...prev, [studentId]: imageBuffer }));
|
||||
} else if (msg.event === 'teacher:webcam-stream-frame') {
|
||||
const { studentId, imageBuffer } = msg.data;
|
||||
setWebcamFrames((prev) => ({ ...prev, [studentId]: imageBuffer }));
|
||||
} else if (msg.event === 'teacher:stream-stopped') {
|
||||
const { studentId } = msg.data;
|
||||
setScreenFrames((prev) => {
|
||||
const next = { ...prev };
|
||||
delete next[studentId];
|
||||
return next;
|
||||
});
|
||||
setWebcamFrames((prev) => {
|
||||
const next = { ...prev };
|
||||
delete next[studentId];
|
||||
return next;
|
||||
});
|
||||
const ws = new WebSocket(wsUrl);
|
||||
wsRef.current = ws;
|
||||
subscribedRef.current = new Set();
|
||||
|
||||
ws.onopen = () => syncSubscriptions(ws);
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
try {
|
||||
const msg = JSON.parse(event.data);
|
||||
if (msg.event === 'teacher:screenshot-stream-frame') {
|
||||
const { studentId, imageBuffer } = msg.data;
|
||||
setScreenFrames((prev) => ({ ...prev, [studentId]: imageBuffer }));
|
||||
} else if (msg.event === 'teacher:webcam-stream-frame') {
|
||||
const { studentId, imageBuffer } = msg.data;
|
||||
setWebcamFrames((prev) => ({ ...prev, [studentId]: imageBuffer }));
|
||||
} else if (msg.event === 'teacher:stream-stopped') {
|
||||
const { studentId } = msg.data;
|
||||
setScreenFrames((prev) => {
|
||||
const next = { ...prev };
|
||||
delete next[studentId];
|
||||
return next;
|
||||
});
|
||||
setWebcamFrames((prev) => {
|
||||
const next = { ...prev };
|
||||
delete next[studentId];
|
||||
return next;
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error parsing WS message in grid view:', err);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error parsing WS message in grid view:', err);
|
||||
}
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
subscribedRef.current = new Set();
|
||||
if (!closed) {
|
||||
retryTimer = setTimeout(connect, 3000);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
connect();
|
||||
|
||||
return () => {
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
students.forEach((s) => {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
event: 'teacher:unsubscribe',
|
||||
data: { studentId: s.studentRkId },
|
||||
})
|
||||
);
|
||||
closed = true;
|
||||
if (retryTimer) clearTimeout(retryTimer);
|
||||
const ws = wsRef.current;
|
||||
if (ws?.readyState === WebSocket.OPEN) {
|
||||
subscribedRef.current.forEach((id) => {
|
||||
ws.send(JSON.stringify({ event: 'teacher:unsubscribe', data: { studentId: id } }));
|
||||
});
|
||||
}
|
||||
ws.close();
|
||||
ws?.close();
|
||||
wsRef.current = null;
|
||||
subscribedRef.current = new Set();
|
||||
};
|
||||
}, [studentIdsString]);
|
||||
|
||||
useEffect(() => {
|
||||
const ws = wsRef.current;
|
||||
if (ws) syncSubscriptions(ws);
|
||||
}, [onlineKey]);
|
||||
|
||||
// Clean up frames when students are removed or go offline
|
||||
useEffect(() => {
|
||||
setScreenFrames((prev) => {
|
||||
|
||||
Reference in New Issue
Block a user