Skip to content

jodonnell24/PTZ

Repository files navigation

PTZ Telepresence Relay

PTZ is a privacy-preserving telepresence prototype for controllable cameras. It relays media through LiveKit and routes PTZ commands through a backend control plane, so viewer browsers can watch and control a camera without receiving the camera's network address.

Highlights

  • WebRTC media relay through LiveKit; media is never sent directly between viewer and camera networks.
  • Express backend for LiveKit token issuance, short-lived control sessions, and PTZ command arbitration.
  • WebSocket control relay with one active controller per room, short leases, rate limits, and command acknowledgements.
  • Browser viewer and browser camera-uplink pages for local demos.
  • Node camera agent for hardware PTZ control, with mock, VISCA, ONVIF, HTTP-template, and HTTP-form adapters.
  • Optional headless examples for camera-side media/control processes.

Architecture

[Camera Edge] --(WebRTC publish)--> [LiveKit SFU] --(WebRTC subscribe)--> [Viewer Browser]
     |                                      ^
     |                                      |
     +--(WS control, outbound only)--> [Backend API/Control Relay] <--(WS control)--+

Viewer only sees backend/SFU public addresses, not camera LAN/WAN IP.

What is implemented

  • POST /api/token returns:
    • LiveKit access token
    • control websocket URL
    • signed control session token, valid for 15 minutes
  • GET /healthz health endpoint
  • /ws/control websocket relay with:
    • signed control-session authentication
    • shared viewer/camera token checks when configured
    • single-controller lock (request_control / release_control)
    • PTZ rate limit using a token bucket
    • PTZ forwarding to the active camera endpoint
    • command acknowledgements from the camera side
  • Audit log for forwarded PTZ commands (logs/ptz-audit.log)
  • Viewer app at /
  • Camera publisher app at /camera
  • Headless camera control agent at src/camera-agent.js
  • PTZ adapters for mock, VISCA over UDP/TCP, VISCA serial, ONVIF ContinuousMove, and generic HTTP-template/form control

Security model

  • Viewer sessions receive relay URLs and signed session tokens, never camera LAN/WAN addresses.
  • Camera-side processes initiate outbound connections to the backend and media relay, so the camera network does not need inbound ports.
  • Control commands are accepted only from the current controller for a room, and the lease is extended only when valid commands are forwarded.
  • Local development can run without shared access tokens. In NODE_ENV=production, the backend refuses to start unless presentation, camera-agent, LiveKit, and control-signing secrets are configured.
  • This repo uses shared presentation/camera-agent tokens rather than end-user accounts. Account auth, durable audit storage, TURN, and camera-specific motion bounds are deployment concerns outside this prototype.

Local run

  1. Install dependencies:
npm install
  1. Start a local LiveKit server:
docker compose up -d livekit
  1. Configure local env:
cp .env.example .env
  1. Start the backend:
npm run dev
  1. Open two browser tabs:
  • Camera/media tab: http://localhost:3000/camera
  • Viewer tab: http://localhost:3000/
  1. In the camera tab, start the camera session. In the viewer tab, connect, acquire control, and use the PTZ buttons.

Shareable viewer link

npm run presentation-link builds a viewer URL that auto-connects and requests PTZ control. If PRESENTATION_ACCESS_TOKEN is configured on the backend, use the same value when generating the link.

PUBLIC_URL=https://your-backend.example.com \
PRESENTATION_ACCESS_TOKEN=replace-with-link-token \
npm run presentation-link

The generated URL has this shape:

https://your-backend.example.com/?room=demo-room&identity=presenter&autoconnect=1&autocontrol=1&access=...

When opened, the viewer page:

  • requests a viewer token from the backend
  • connects to the LiveKit media relay
  • connects to the backend control relay
  • requests PTZ control
  • displays a privacy panel showing the media relay, control relay, and that no camera edge address was issued to the browser

Hardware integration

For media, the simplest path is the browser camera page with an HDMI capture device, USB/UVC camera, OBS virtual camera, or another browser-visible source.

For PTZ control, run the Node camera agent on the camera-side machine:

PTZ_DRIVER=visca-udp PTZ_VISCA_HOST=192.0.2.10 npm run camera-agent

Supported driver values:

  • mock
  • visca-udp
  • visca-tcp
  • visca-serial
  • onvif
  • http-template
  • http-form

The browser camera page can publish video while the camera agent owns the PTZ control socket. Leave "Register this browser as the PTZ receiver" unchecked for that split setup.

Browser-free camera video can also run from a headless process on the camera-side machine. This path requires ffmpeg on the system path.

python3 -m venv .venv-headless
. .venv-headless/bin/activate
pip install -r requirements-headless.txt

BACKEND_HTTP_URL=https://ptz-backend.example.com \
CAMERA_AGENT_TOKEN=replace-with-camera-agent-token \
RTSP_URL=rtsp://viewer:replace-with-password@192.0.2.10:554/stream1 \
python scripts/headless-video-agent.py

This joins LiveKit as the camera publisher and pushes frames from the camera RTSP stream without using the browser camera-uplink page.

Deployment examples

The backend needs these variables for a shared-token deployment:

LIVEKIT_URL=wss://...
LIVEKIT_API_KEY=...
LIVEKIT_API_SECRET=...
CONTROL_SIGNING_SECRET=...
PRESENTATION_ACCESS_TOKEN=...
CAMERA_AGENT_TOKEN=...

Reference env files and systemd templates live in deploy/:

  • deploy/pi/camera-agent.env.example
  • deploy/pi/ptz-camera-agent.service
  • deploy/pi/video-rtmp.env.example
  • deploy/pi/ptz-video-rtmp.service
  • deploy/pc/headless-video.env.example

These are templates for camera-side machines. Paths, Linux users, video devices, RTMP ingress values, and camera driver settings should be adapted to the target hardware.

Validation

npm run check
npm audit --audit-level=moderate

npm run check syntax-checks the backend, camera agent, helper scripts, and browser scripts.

About

Privacy-preserving PTZ camera relay with WebRTC media and mediated camera control.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors