Tell a client where to watch a printer's camera
`continuum-proxy` now generates go2rtc's configuration from the camera
URLs the printers themselves report, and sends two new things up: a
`stream_base_url` on its hello, and a `camera_stream` per printer in
telemetry. This is the other end of both.
`GET /printers/:id/stream` answers `{ streamUrl, cameraId }`, which is
everything a browser needs to open a WebRTC session against the gateway's
go2rtc. 404 rather than 200-with-nulls when either half is missing: a
client handed `{ streamUrl: null }` has to remember to check, and one that
forgets renders a player that can never load. The two causes get
different messages, because they are different problems for whoever is
debugging - a P1/A1 that can never stream is not a gateway that happens
to be offline - and the printer is checked first so a disconnected
gateway cannot mask the permanent case.
The resolution lives in `src/stream.ts` with no imports at all, for the
same reason `dispatch/job_status.ts` does: `../db` opens a connection at
import time, and these two messages are exactly the part worth testing
without one.
Storage, both decided against the obvious alternative:
- `stream_base_url` lives on the in-memory connection, so it goes away
with the socket. It is a LAN address that is only reachable while that
gateway is up; a column on `farms` would keep handing out an address
nothing answers after the gateway disconnected or moved.
- `camera_stream` gets a plain column, because every sibling telemetry
field already has one, and it is cleared when absent like the rest - a
printer that loses its stream must stop being offered one.
`cameraStream` is also added to `printerProjection`, which that file
deliberately requires someone to decide per column. It is a go2rtc stream
name, which is our own printer id echoed back, not a credential, and
having it in the list a client already fetches is what saves it from
calling the stream endpoint once per printer to discover that most of
them 404 by design.
Worth writing down, and it is in the route's comment too: `streamUrl` is
a LAN address and nothing relays it. A browser on the gateway's network
can play the stream and one outside cannot, and this endpoint cannot tell
which the caller is. Good enough for an operator in the workshop, a real
limit for anyone else, and a relay is the fix that does not exist yet.
85 tests (was 77), `bunx tsc --noEmit` clean.
This commit is contained in:
@@ -39,6 +39,15 @@ interface EdgeConn {
|
||||
gatewayId: string;
|
||||
sessionId: string;
|
||||
lastSeenAt: number;
|
||||
/**
|
||||
* Where this gateway serves camera restreams, from its hello. Lives on the
|
||||
* connection rather than in a DB column or a farm-keyed map: it is a LAN
|
||||
* address that is only reachable while this gateway is up, so it should
|
||||
* vanish with the socket instead of outliving it as a URL nothing answers.
|
||||
* The alternative, a column on `farms`, would keep serving a stale address
|
||||
* after the gateway disconnected or moved. Undefined means none advertised.
|
||||
*/
|
||||
streamBaseUrl?: string;
|
||||
send: (message: EdgeServerMessage) => void;
|
||||
/**
|
||||
* Gateway printer id -> our `printers.id`. Filled on the first telemetry
|
||||
@@ -217,6 +226,9 @@ async function persistTelemetry(conn: EdgeConn, batch: PrinterTelemetry[]) {
|
||||
remainingMinutes: snapshot.remaining_minutes ?? null,
|
||||
currentJobName: snapshot.job_name ?? null,
|
||||
activeAlerts: snapshot.active_alerts ?? null,
|
||||
// Cleared like the rest when absent: a printer that lost its stream
|
||||
// must stop being offered one.
|
||||
cameraStream: snapshot.camera_stream ?? null,
|
||||
lastSeenAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
@@ -288,6 +300,16 @@ export function isGatewayOnline(farmId: string): boolean {
|
||||
return wsId !== undefined && connections.has(wsId);
|
||||
}
|
||||
|
||||
/**
|
||||
* The stream base URL the farm's currently connected gateway advertised, or
|
||||
* undefined if no gateway is connected or it advertised none. Deliberately
|
||||
* does not distinguish those two: callers only need to know it is unusable.
|
||||
*/
|
||||
export function getStreamBaseUrl(farmId: string): string | undefined {
|
||||
const wsId = gatewayByFarm.get(farmId);
|
||||
return wsId ? connections.get(wsId)?.streamBaseUrl : undefined;
|
||||
}
|
||||
|
||||
export const edgeWsRoute = new Elysia()
|
||||
.use(jwt({ name: "edgeJwt", secret: edgeJwtSecret }))
|
||||
.ws("/ws/edge/v1", {
|
||||
@@ -334,6 +356,8 @@ export const edgeWsRoute = new Elysia()
|
||||
|
||||
conn.farmId = farmId;
|
||||
conn.gatewayId = message.gateway_id;
|
||||
// `?? undefined`: absent and explicit null both mean "no streams".
|
||||
conn.streamBaseUrl = message.stream_base_url ?? undefined;
|
||||
|
||||
// One gateway per farm: drop whatever was registered before. On a
|
||||
// reconnect that stale socket is already dead, and leaving it
|
||||
|
||||
Reference in New Issue
Block a user