Files
MultiRoombaRover/webui/src/hooks/useVideoRequests.js
T
2025-12-23 22:27:35 -05:00

117 lines
3.2 KiB
JavaScript

import { useEffect, useMemo, useRef, useState } from 'react';
import { useSocket } from '../context/SocketContext.jsx';
function normalizeEntry(entry) {
if (!entry) return null;
if (typeof entry === 'string') {
const id = String(entry).trim();
if (!id) return null;
return { type: 'rover', id, key: id };
}
if (typeof entry === 'object') {
if (entry.type && entry.id) {
const id = String(entry.id);
let key = entry.key;
if (!key) {
key = entry.type === 'room' ? `room:${id}` : id;
}
return {
type: entry.type,
id,
key,
};
}
if (entry.roverId) {
const id = String(entry.roverId);
return { type: 'rover', id, key: entry.key || id };
}
if (entry.roomCameraId) {
const id = String(entry.roomCameraId);
return { type: 'room', id, key: entry.key || `room:${id}` };
}
}
return null;
}
function dedupeEntries(entries = []) {
const seen = new Set();
const unique = [];
entries.forEach((entry) => {
if (!entry?.key || seen.has(entry.key)) {
return;
}
seen.add(entry.key);
unique.push(entry);
});
return unique.sort((a, b) => a.key.localeCompare(b.key));
}
export function useVideoRequests(sourceList = [], options = {}) {
const socket = useSocket();
const { enabled = true, version = null } = options;
const [sources, setSources] = useState({});
const normalizedEntries = useMemo(() => {
if (!Array.isArray(sourceList)) {
return [];
}
return dedupeEntries(sourceList.map(normalizeEntry).filter(Boolean));
}, [sourceList]);
const normalizedKey = useMemo(() => {
const base = normalizedEntries.map((entry) => `${entry.type}:${entry.id}:${entry.key}`).join('|');
return version ? `${base}|v:${version}` : base;
}, [normalizedEntries, version]);
const entriesRef = useRef([]);
const [connectionNonce, setConnectionNonce] = useState(0);
useEffect(() => {
if (!socket) return undefined;
const handleConnect = () => setConnectionNonce((prev) => prev + 1);
socket.on('connect', handleConnect);
return () => {
socket.off('connect', handleConnect);
};
}, [socket]);
useEffect(() => {
entriesRef.current = normalizedEntries;
}, [normalizedEntries]);
useEffect(() => {
if (!normalizedKey || !enabled) {
setSources({});
return undefined;
}
const entries = entriesRef.current;
let cancelled = false;
function requestEntry(entry) {
const payload = entry.type === 'room' ? { roomCameraId: entry.id } : { roverId: entry.id };
socket.emit('video:request', payload, (resp = {}) => {
if (cancelled) return;
setSources((prev) => ({ ...prev, [entry.key]: resp }));
});
}
entries.forEach((entry) => {
requestEntry(entry);
});
return () => {
cancelled = true;
};
}, [socket, normalizedKey, enabled, connectionNonce]);
const filtered = useMemo(() => {
if (!normalizedKey || !enabled) return {};
const next = {};
normalizedEntries.forEach((entry) => {
if (sources[entry.key]) {
next[entry.key] = sources[entry.key];
}
});
return next;
}, [normalizedKey, sources, normalizedEntries]);
return filtered;
}