Cameras on devices (openpanel C1): frames in memory, a binary uplink, demand

- Volatile frames are held in memory with a byte-bounded queue instead of
  the /dev/shm ring scanned on every put; workers keep writing to the
  directory, which is trimmed on a timer. A volatile upload no longer spools
  to disk. ARTIFACT_VOLATILE_BYTES=0 turns the ring off.
- A device sends camera frames as binary messages on its socket, held and
  published in one step and credited to its device node; refused frames are
  logged and dropped. Its snapshot carries demand and the uplink (binary, or
  http through the portal).
- One demand registry for every socket's wanted media, the tunnel's
  included, plus what running flows read, mapped onto on-demand ports and
  published as device_demand; at most 8 media names per socket.
- Per-message deflate off in serve and dev; a device may fetch the last 8
  digests of a message; published dashboards cached per store revision; a
  revoked device socket is closed with 1008.
- Media tiles want bytes only while on screen; the Devices tab previews a
  wired camera; the device node warns when a flow reads an on-demand port.
This commit is contained in:
Melvin Strobl
2026-10-16 20:43:14 +02:00
parent d79203a5f7
commit ecc4bee4bb
29 changed files with 1425 additions and 220 deletions
+47
View File
@@ -0,0 +1,47 @@
import {
type RefObject,
useEffect,
useState,
useSyncExternalStore,
} from "react"
function subscribeVisibility(listener: () => void) {
document.addEventListener("visibilitychange", listener)
return () => document.removeEventListener("visibilitychange", listener)
}
/** Whether the tab is the one being looked at. */
export function usePageVisible(): boolean {
return useSyncExternalStore(
subscribeVisibility,
() => document.visibilityState === "visible",
() => true,
)
}
/**
* Whether an element is on screen: inside the viewport, in a tab that is
* being looked at.
*
* Off until the observer first says otherwise, so a tile below the fold never
* asks for anything on its way to being found out of view.
*/
export function useOnScreen(ref: RefObject<Element | null>): boolean {
const page = usePageVisible()
const [inView, setInView] = useState(
() => typeof IntersectionObserver === "undefined",
)
useEffect(() => {
const element = ref.current
if (!element || typeof IntersectionObserver === "undefined") return
const observer = new IntersectionObserver((entries) => {
const last = entries[entries.length - 1]
if (last) setInView(last.isIntersecting)
})
observer.observe(element)
return () => observer.disconnect()
}, [ref])
return page && inView
}