SDK · Cameras
Snapshots with their age, and refcounted live sessions handed to a LiveKit client.
Generated from the SDK’s own source. The SDK Reference is the narrative; this page is every member, in the order the SDK declares them.
CamerasApi
A robot’s cameras, reachable as client.cameras: what exists, the latest
still frame, and a live video session. All of it is REST — no realtime
channel is involved.
list()
list(robotId: string): Promise<({ slug: string; width: number; height: number; fps: number; snapshot_interval_seconds: number })[]>
Every camera exposed on this robot, as descriptors — the same per-robot list the other kinds use.
| Parameter | Type | Required | Description |
|---|---|---|---|
robotId |
string |
yes |
Returns Promise<({ slug: string; width: number; height: number; fps: number; snapshot_interval_seconds: number })[]>.
snapshot()
snapshot(robotId: string, slug: string): Promise<CameraSnapshot>
The current snapshot: image bytes plus its age. Independent of live —
a snapshot keeps updating on snapshot_interval_ms whether or not
anyone is watching live, and keeps being served, with a growing
age, even while the bridge is offline.
| Parameter | Type | Required | Description |
|---|---|---|---|
robotId |
string |
yes |
|
slug |
string |
yes |
Returns Promise<CameraSnapshot>.
References: CameraSnapshot
snapshotMeta()
snapshotMeta(robotId: string, slug: string): Promise<CameraSnapshotMeta>
The snapshot’s metadata alone — for polling “is there a newer frame
yet?” without re-downloading the image on every check. Prefer this over
snapshot for a view that only needs to show an age (e.g. “updated 2s
ago”) and fetches pixels on demand.
| Parameter | Type | Required | Description |
|---|---|---|---|
robotId |
string |
yes |
|
slug |
string |
yes |
Returns Promise<CameraSnapshotMeta>.
References: CameraSnapshotMeta
live()
live(robotId: string, slug: string): Promise<CameraLiveSession>
Takes a refcounted hold on this camera’s live stream: the
first live() on a slug starts the robot publishing, the last viewer
leaving stops it. Deliberately not deduplicated locally across multiple
live() calls for the same (robotId, slug) — unlike a datapoint
subscription, each call needs its own distinct LiveKit participant, so
a local counter here would just be the same shared-count bug the
subscription layer already fixed, self-inflicted on a resource the
cloud counts correctly on its own.
| Parameter | Type | Required | Description |
|---|---|---|---|
robotId |
string |
yes |
|
slug |
string |
yes |
Returns Promise<CameraLiveSession>.
References: CameraLiveSession
CameraSnapshot
One snapshot read: the image bytes plus everything needed to state how old they are.
| Property | Type | Required | Description |
|---|---|---|---|
image |
Uint8Array<ArrayBufferLike> | null |
yes |
The encoded image, or null when nothing has been captured yet. |
Inherited from CameraSnapshotMeta: mime, width, height, timestamp_ms, age_ms.
References: CameraSnapshotMeta
CameraSnapshotMeta
The snapshot’s metadata alone, without the bytes. All fields
are null together when nothing has been captured yet for this camera —
a fresh configuration before the first snapshot_interval_ms elapses, say
— which is a state, not a failure: the wire answers it with
no_snapshot_yet, and both snapshot/snapshotMeta absorb that code
here rather than throw it, so a caller checks age_ms === null instead of
wrapping every poll in a try/catch for something that is not exceptional.
age_ms is always the cloud’s own figure, never recomputed client-side
as Date.now() - timestamp_ms: the cloud is the one clock that knows how
long it has actually held the frame, and recomputing would reintroduce the
viewer’s own clock skew as a source of lying about freshness (see
SNAPSHOT_HEADERS’s doc comment in @fleetless/contracts).
| Property | Type | Required | Description |
|---|---|---|---|
mime |
string | null |
yes |
The image’s media type, e.g. image/jpeg. |
width |
number | null |
yes |
The image’s width in pixels. |
height |
number | null |
yes |
The image’s height in pixels. |
timestamp_ms |
number | null |
yes |
The bridge’s capture time, in unix milliseconds — when the frame was taken, not when it was served. |
age_ms |
number | null |
yes |
How long the cloud has held this frame, in milliseconds. The one figure to read for freshness. |
CameraLiveSession
What a LiveKit client needs to join, plus the means to leave. Returned by
cameras.live. Hand url and token straight to a LiveKit client SDK
(e.g. Room.connect(url, token)) — this SDK stops there on purpose: no
video widget and no teleop-style helper, so that the app owns how the
video is presented.
| Property | Type | Required | Description |
|---|---|---|---|
session_id |
string |
yes |
This viewer’s own hold — what release() releases, and the only thing distinguishing this session from every other tab of the same identity watching the same camera. |
url |
string |
yes |
The LiveKit server URL to connect to. |
room |
string |
yes |
The LiveKit room this session joins. |
token |
string |
yes |
The LiveKit access token for this session. It is checked when the participant connects and not again afterwards, so it bounds joining, not the session: a viewer who has already joined keeps receiving video past expires_at. What ends a joined session is release() together with disconnecting the room, the cloud reconciling the hold away against LiveKit’s real participants, or a revocation — a membership, role or key change — kicking the participant out. |
expires_at |
string |
yes |
When this token can no longer be used to join — not when an already-joined session ends. LiveKit checks a token at connect time only, so a Room that joined before this timestamp keeps streaming past it untouched; this field bounds how long an unused token sits around, nothing more. It is not a backstop for a forgotten release(), a crash, or a kill -9 after joining: what ends an already-joined session is release() plus disconnecting the Room, the cloud noticing (via its own reconciliation against LiveKit’s actual room participants) that this viewer is gone, or revocation kicking the participant outright. Do not design around expires_at as if it were any of those. |
release()
release(): Promise<void>
Tells the cloud this viewer no longer wants to hold the camera live —
this hold, addressed by session_id, and no other tab’s. Each
CameraLiveSession releases only the hold it itself took, so one tab’s
cleanup never stops the robot out from under another tab of the same
logged-in user.
This alone does not stop the stream. The cloud makes LiveKit room
participation the authoritative refcount, not this call — precisely
because a closing tab cannot be relied on to make it. release() is a
courteous fast path; the robot actually stops publishing once every
viewer’s LiveKit Room has disconnected, which the SFU notices on its
own with no cooperation required. Always pair this with disconnecting
the Room you connected with url/token — see the Cameras
section of the SDK reference for the paired cleanup pattern; a release() that ran
alone while the Room stayed connected would stop nothing.
Safe to call more than once (only the first call does anything) and
never rejects — this is a courtesy notification, not the thing that
actually stops the stream (see above), so there is nothing a caller
could usefully do with a rejection here. That also makes this safe to
use directly as e.g. a React effect’s cleanup return value, including
from beforeunload, where a call that could throw would be a liability.
A failed DELETE here is not observable anywhere — not as a rejection, a realtime event, or a field on this object. Deliberate, not an oversight: the only consumer of that information would be code deciding whether to retry, and the reconciliation backstop described above already makes a retry unnecessary for correctness. A future need to know “did my release actually reach the cloud” (telemetry, say) is a new, additive signal to design, not a change to this method’s contract.
Returns Promise<void>.
CameraDescriptor
The camera-descriptor wire schema, re-exported so app code and the SDK agree on the shape — see API Schemas for its fields.