import { useEffect, useRef, useState } from 'react' import { streamCameraLive } from '../api.js' // The live view of one camera. // // It is a few frames a second, not video, and the label says so rather than // letting somebody conclude the camera is stuttering. That is the honest limit // of relaying through the agent's outbound connection; true 25 fps would need // WebRTC and a TURN server, which is a different piece of infrastructure. // // **Nothing is uploaded from the shop until this component is mounted**, and it // stops within seconds of it going away. That is the whole reason a live view // is affordable at all, and why this is a deliberate action rather than // something every camera card does on its own. export default function CameraLive({ camera, onClose }) { const img = useRef(null) const [state, setState] = useState('waiting') // Nothing has arrived after a sensible wait. Its own state, because "the // shop PC has not answered" is a different thing to tell somebody than // "connecting", and leaving a spinner up forever tells them nothing at all. const [stalled, setStalled] = useState(false) useEffect(() => { const ctrl = new AbortController() const stop = streamCameraLive({ cameraId: camera.id, img, onState: s => { setState(s); if (s === 'live') setStalled(false) }, signal: ctrl.signal, }) // Generous: the server holds the agent's poll, the agent then has to reach // the camera, and a cold start is a couple of seconds even when everything // works. const t = setTimeout(() => setStalled(true), 12000) return () => { stop(); ctrl.abort(); clearTimeout(t) } }, [camera.id]) // What to say when nothing arrives. The two causes need different actions - // one is the camera, the other is the PC - so they must not share a message. const stalledNote = camera.connected === false ? 'This camera was not connecting when the shop PC last reported. Check it is powered on and reachable on the shop’s network.' : camera.connected == null ? 'The shop PC has not reported on this camera yet. It may still be starting up.' : 'The shop PC is not sending frames. It may be offline, or its Behavision app may not be running.' const note = stalled && state !== 'live' ? stalledNote : { waiting: 'Asking the shop PC…', live: 'Live', reconnecting: 'Reconnecting…', }[state] return (