M04 — Camera Ingestion and Live Viewing
One-line goal: an operator opens the dashboard, sees a registered camera’s live feed within 2 seconds via WebRTC, and the camera-manager service is extracting one frame per second to the AI pipeline.
After M04, real cameras are producing real frames. The platform is no longer abstract.
Tracks involved
- Edge — primary. Camera Manager (Python container under the supervisor), ONVIF discovery, RTSP connection management, MediaMTX integration, frame extraction.
- Cloud Infra — MediaMTX deployment in the cloud (the WebRTC relay).
- Backend — camera registration endpoints (extend M03’s CameraService), stream-start signalling.
- Frontend — camera grid with WebRTC playback, online/offline indicators.
Dependencies
- M01 complete (edge supervisor scaffolding, MediaMTX containers in registry).
- M02 complete (operators are authenticated; only authorized users see live streams).
- M03 complete (camera registry exists in Postgres).
Deliverables
1. Edge Camera Manager container
A Python service in apps/edge-app/camera_manager/ implementing the CameraTransport abstraction (packages/contracts/04-abstractions §5.1).
OnvifRtspTransportas the pilot driver:- WS-Discovery for finding cameras on the LAN.
- ONVIF over digest auth for capability discovery and profile selection.
- RTSP client (using
onvif-zeep+aiortspor similar) for stream URL retrieval.
- Reconnect-with-backoff on stream failure: 1 s → 2 s → 4 s → 8 s → 16 s → 30 s (max), with jitter.
- Camera-offline detection: 5 minutes without a successful frame → mark camera offline, emit
CameraOfflineEventto the tunnel. - Per-camera health metrics: frame rate observed, decode errors per minute, last-frame timestamp.
- Credential storage: per-camera credentials encrypted at rest in the edge supervisor’s SQLite store, fetched from Secret Manager during registration.
2. Frame extraction pipeline
- At 1 fps (configurable per camera, default 1 fps) the camera manager grabs a frame from each active stream.
- Frame is JPEG-encoded at 75% quality, typically ~50 KB.
- Frame plus metadata (
camera_id,building_id,tenant_id,captured_at) becomes aFrameEventper the contracts package. - Sent to a local Tier-0 motion filter (M05) — frames that pass go through the edge tunnel to the cloud
frame-eventsPub/Sub topic. Frames that fail are discarded with a count maintained for observability.
3. Cloud-side MediaMTX deployment
- MediaMTX deployed in the cluster (
streamingnamespace), exposing a WHEP endpoint behind the mesh gateway. - Two roles:
- Live stream relay — receives WebRTC streams pushed from the edge MediaMTX (over the mTLS tunnel via WebRTC’s standard signalling) and forwards to browser viewers via WHEP.
- On-demand stream coordination — receives stream-start commands from the API, signals the edge to start the stream, manages teardown when no viewers remain.
- TLS via the mesh gateway; clients see only HTTPS.
- HPA on concurrent viewer count.
4. Edge-side MediaMTX
- Runs as a container under the edge supervisor.
- Configuration generated from the camera registry — each camera has a path like
/cam-{camera_id}. - Converts RTSP-pulled-from-camera to WebRTC-pushed-to-cloud, and to WebRTC-served-to-local-LAN (for the local building dashboard).
- Stream activation: idle by default; the supervisor receives a stream-start command (via the tunnel) and instructs MediaMTX to begin pulling from the camera. On stream-stop (or 5 min of no viewers), the camera-side connection closes.
5. Stream-start signalling
The flow when an operator clicks a camera:
- Browser calls
POST /v1/cameras/{id}/stream/starton the API. - API authenticates, verifies operator has access to this camera’s building.
- API publishes a
StreamStartCommandto a tenant-scoped command channel on the edge tunnel. - Edge supervisor receives the command, validates it, instructs its MediaMTX to start.
- Edge MediaMTX establishes a WebRTC publishing connection to the cloud MediaMTX.
- Cloud MediaMTX advertises the stream is available via WHEP.
- Browser begins WebRTC negotiation via WHEP and receives video within ~2 s of the original click.
- When the browser closes the stream (or session ends), reverse signalling tears it down.
Stream-start commands are audit-logged with actor_user_id, camera_id, and timestamp.
6. Camera registration UX
- Admin user navigates to a building → “Cameras” tab → “Discover cameras”.
- The API forwards a discovery request to the edge supervisor; the supervisor runs ONVIF discovery; results come back as a list.
- Admin selects which cameras to register; enters credentials per camera (or per-batch).
- Cameras saved to Postgres with
status = 'pending_verification'. - Edge supervisor verifies each by attempting a snapshot; success →
status = 'online'; failure → status reflects the error, retry available.
7. Camera health monitoring
- Each edge supervisor emits
EdgeTelemetryEventper01-architecture-summary.mdand the contracts package. - The API consumes these and updates the camera registry’s
last_seen_atandstatusfields. - An alert engine rule (basic version exists from M01; full version in M07) emits a
camera_offlinealert if a camera’slast_seen_atis older than its expected check-in interval.
8. Frontend — camera grid
apps/dashboard/src/views/CameraGrid.tsx:- 1 / 4 / 9 / 16 / 25 grid layouts.
- Per-tile: camera name, online status indicator, current FPS estimate.
- WebRTC player using a minimal WHEP client (or
video.jswith WHEP plugin). - Click a tile to enlarge (or to go full-screen).
- Multi-stream support: opening multiple tiles concurrently triggers a stream-start per camera (each independently authorized).
- Saved layout presets per user (in Postgres
user.preferencesJSONB). - Spatial-aware “auto-layout” hook (used by M07 — show cameras near the active alert), placeholder implementation in M04.
9. Frontend — single camera detail
- Larger viewer.
- PTZ controls if the camera supports them (calls API → command → edge MediaMTX → camera ONVIF PTZ).
- Snapshot button (calls API → edge → returns a fresh frame).
- Recent AI events for this camera (populated in M05).
- Audit log: who has viewed this camera recently (auditor / admin only).
10. Bandwidth management
- Default profile: 1080p / H.264 / 2-5 Mbps per stream.
- Adaptive fallback: if a client’s WebRTC reports packet loss, automatically renegotiate at 720p / 1.5-3 Mbps.
- Only active when a viewer is connected — idle = 0 bandwidth.
Verification
- A registered camera shows up in the dashboard. Discovery finds the camera; admin registers it; within 30 seconds the camera appears in the operator’s grid with the
onlineindicator. - Live video plays within 2 seconds. Click → first frame visible in ≤ 2 s, measured on a representative network connection (Saudi internal, not transcontinental).
- Cable-pull test. Disconnecting the camera from the network triggers a
camera_offlinealert within 5 minutes. Reconnecting clears the alert and resumes streaming within 30 seconds. - Frame extraction confirmed. While streaming,
frame-eventsPub/Sub topic receives one message per second per camera (or whatever the configured rate is). Messages contain valid JPEG data and correct metadata. - Multi-camera grid works. 4 cameras streaming simultaneously in a 2×2 grid; CPU on the edge box stays under 50%; cloud MediaMTX handles all four; browser plays smoothly.
- Stream teardown is automatic. Close the dashboard tab; within 10 seconds, the edge supervisor reports the stream is stopped and the camera-side RTSP connection is closed.
- Cross-tenant denial. Operator from Building A attempts
POST /v1/cameras/{building-B-camera-id}/stream/start. Returns 404. Audit log captures the attempt. - PTZ works on a camera that supports it. Sending a pan-left command via the UI rotates the camera as expected. PTZ command is audit-logged.
- Snapshot endpoint works.
POST /v1/cameras/{id}/snapshotreturns a fresh JPEG within 1 second. - Reconnect handles network blip. Manually toggle the edge box’s network interface for 30 seconds; cameras reconnect afterwards without manual intervention; no permanent state loss.
Risks
| Risk | Likelihood | Mitigation |
|---|---|---|
| Camera vendor’s ONVIF implementation is buggy | High (universal experience) | Compatibility matrix in apps/edge-app/camera_manager/COMPATIBILITY.md; add quirks per vendor in code |
| RTSP authentication fails silently | Medium | Health-check on every camera at register-time confirms credentials; clear error messaging |
| Saudi Airlines uses an NVR with no direct camera access | Medium | NVR adapter contingency per 05-contingencies.md §6 |
| WebRTC NAT traversal fails for some operator networks | Medium | TURN server provisioned in Dammam; fallback path documented |
| Bandwidth at Arafat is constrained during Hajj | Medium | On-demand streaming + adaptive quality + Tier 0 filtering keeps baseline usage low |
| Camera passwords leak through logs | High impact / Low probability | Credentials redacted in log middleware; verified manually |
Open questions
- Specific camera vendor at the Arafat site — affects which ONVIF quirks we’ll hit. Site survey reveals; contingency for vendor-specific drivers in
04-abstractions-and-contracts.md§5.1. - NVR vs direct access — Q2 in the reminders list. Affects whether we use
OnvifRtspTransportdirectly orOnvifNvrTransport. Pilot driver should be writeable for either case; deploy decision at site time. - PTZ availability — some cameras have it, some don’t. The UI hides PTZ controls when
is_ptz = false. - Frame rate per camera — default 1 fps is conservative. Crowd-monitoring zones (prayer halls) may warrant 2-3 fps; static corridors maybe 0.5 fps. Per-camera config available; tuning in M05.
Exit criteria
All 10 verification items pass on a real camera connected to a real (or developer-emulated) edge box. Sign-off entry in docs/plan/COMPLETION_LOG.md.