For the complete documentation index, see llms.txt. This page is also available as Markdown.

Private Cameras

Analyze cameras behind a firewall with EyePop cloud inference

Use this path when cameras stay on a private network but inference runs in EyePop cloud. It is different from an on-premise runtime, which runs inference inside your infrastructure.

Choose an integration

Pattern
Video path
Who operates the on-premise component
Best fit

VMS plugin

The plugin pushes MPEG-TS over HTTPS

Customer

A VMS with a supported plugin runtime

Customer connector

A customer service pushes MPEG-TS over HTTPS

Customer

A custom integration without a VMS plugin

Camera relay

Your application reads RTSP locally and pushes MPEG-TS over HTTPS

Customer

An RTSP camera, without running a VPN

Managed Tailscale

The cloud worker reads RTSP across the tailnet

Customer

A managed, private network integration

No pattern requires an inbound firewall port: video is either pushed outbound from inside the network, or carried over a mutually authenticated VPN overlay. With cloud inference, encoded video leaves the premises over TLS or WireGuard. Choose an on-premise runtime if video must be processed locally.

Connect a camera on a private network

A camera on a private LAN has no route in from outside it, and closing that gap is usually the hardest part of the integration. Port forwarding exposes the camera to the internet. A hand-rolled VPN or reverse tunnel works, but it is yours to run and keep up. On-premise installs hit a third problem: a container on a Docker bridge network cannot reach a LAN camera without host networking or an explicit route.

Tailscale is the shortest way through. Install it on the camera's network and on whatever reads the stream, and both sides sit on one tailnet — a stable address, no inbound firewall rule, no port forwarding.

Setting up the tailnet is yours to do today; the CLI does not configure it for you. For the managed pattern above, where an EyePop cloud worker reads RTSP across your tailnet, contact support@eyepop.ai.

Relay a camera instead of exposing it

A tailnet is not the only way through, and it is not the shortest. Your application can read the camera on your own network and forward the stream to EyePop over the same outbound HTTPS every other push pattern uses. Nothing has to be reachable from the internet, there is no VPN to operate, and the camera's credentials never leave your network.

The relay remuxes the stream — it copies the encoded video into MPEG-TS without decoding or re-encoding it. That is what keeps the cost negligible and the quality identical to the camera's own output; a relay is not a transcoder and does not need a GPU.

Relaying with the Python SDK

Relaying needs the relay extra, which pins the media library used to read the camera. Requires eyepop 3.21.1 or newer:

rtsp_relay_stream() opens the camera and hands you its video as a stream of bytes, with the capture times already travelling inside it. Pass that stream to the worker the same way you would any other upload:

The camera is read on your network and its stream forwarded to the worker; predictions come back as they would from any other source, each carrying captured_at.

Errors and retrying are yours to handle. One call relays one RTSP session, and the SDK does not retry on your behalf — a decision rather than an omission, because a reconnect has to become a new upload: a single MPEG-TS stream cannot carry two RTSP sessions without renumbering the timestamps the capture times depend on.

So a camera that reboots, loses power, or sits behind a switch that restarts will end the stream, and your application decides what happens next. Three things are worth getting right:

  • Find out why the stream ended. A live camera's stream does not finish on its own, so reaching the end of it means something went wrong — but a camera that simply stops sending looks identical to a file running out, and by the time the failure reaches your prediction loop it may be wrapped in a transport error. Check stream.failure once the stream ends. A CameraError is worth retrying; an UploadError is a question for EyePop and retrying it will usually just hammer a worker that already refused you.

  • Back off between attempts. A camera rebooting takes tens of seconds. Retrying immediately fills your logs and achieves nothing; doubling the delay up to a ceiling of half a minute or so is the usual shape.

  • Expect a fresh start each time. Each attempt is a new session and a new upload, and the capture-time window described below begins again with it.

examples/relay_example.py in eyepop-sdk-python shows all three in about forty lines, as relay_rtsp_source(). Copy it if it suits you, or write your own policy around rtsp_relay_stream().

A finite RTSP source — a recording served over RTSP rather than a live camera — needs no retry policy at all: it ends when it ends, and stream.failure tells you whether that was the end of the recording or a fault.

Relaying is available in the Python SDK today. There is no Node equivalent yet.

Trade-offs against the tailnet pattern:

Camera relay
Managed Tailscale

What you run

A small application reading RTSP

A tailnet node, kept up

Who reaches the camera

Only your application

The EyePop cloud worker

Reconnect and retry

Yours

Yours

Setup

An SDK call

Coordinate with EyePop support

Capture time on a relayed camera

Predictions from a relayed camera carry captured_at: the moment the camera captured the frame, on the camera's own clock, rather than the moment EyePop received it. This matters whenever predictions are correlated with anything outside the stream — another camera, a sensor, a transaction log — because network and buffering delay between the camera and EyePop is variable and would otherwise be baked into every timestamp.

It is the same value, with the same meaning, that a worker reading the camera directly would report.

What it requires of your camera. The camera must emit RTCP sender reports and have a disciplined clock, usually from NTP. This is the ordinary case for business and industrial cameras and is often a setting you must switch on. Point the camera at the same time source as the rest of your infrastructure.

When it is missing. captured_at is left out of a prediction rather than sent as null:

  • At the start of a stream, if your camera is slow to send its clock reference. Any frame forwarded ahead of that reference carries no capture time. In practice the relay rarely forwards one: opening the camera and waiting for the first complete frame takes around two seconds by itself, and most cameras send their reference within that window. Measured against a test camera, every prediction carried a capture time from the first one — the opening moments are not analysed at all, rather than analysed without a timestamp. This is not a cost of relaying: a worker reading the camera directly starts sooner, and so reports its own opening second or so without capture times. A reconnect starts a fresh session, where the same applies.

  • For the whole stream, if the camera has no usable clock. Some cameras send no sender reports at all. The relay warns when it sees this, so the cause is visible rather than silent. No amount of configuration on the EyePop side changes it — it is a property of the camera.

A missing captured_at is never a guessed one. Where the capture time is not known, it is absent.

Integration requirements

  • One persistent session per camera is the recommended default for failure isolation.

  • Push integrations send H.264 in MPEG-TS (video/mp2t) and receive JSONL predictions.

  • The customer owns prediction storage, reconnect and retry, and operation of the on-premise component in every pattern.

  • Confirm network egress, bandwidth, retention, region, and tailnet ACL requirements during deployment design.

Next steps

  • Deployment — the persistent session a camera stream attaches to

  • Source Types — the relay as a source, and the reference implementation to copy

  • On-Premise — open the On-Premise section in this site's navigation to run inference locally instead of sending video out

Last updated