Bridges Alarm.com security camera streams to local RTSP for HomeKit Secure Video (HKSV) via Homebridge.
Alarm.com cameras cannot be accessed directly via RTSP β ADC re-provisions camera credentials via OpenVPN and randomly generates root passwords. The existing homebridge-node-alarm-dot-com plugin handles alarm panel/sensors/locks but has no video support.
This bridge authenticates with Alarm.com's web API, negotiates a WebRTC connection to the camera via ADC's end-to-end signaling protocol, receives the H.264 video stream server-side using werift, and republishes it as a local RTSP stream via ffmpeg β go2rtc.
The approach was proven by kjjohnsen/HomeAssistantADCCameraIntegration, which does the same thing in the browser. This project ports the signaling protocol to Node.js for headless server-side operation.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β adc-video-bridge (Node.js) β
β β
β [AlarmAuth] βββ [TokenManager] β
β β β β
β β [CameraManager] β
β β β β β
β β [CameraStream] Γ N β
β β β β β
β β [ADC Signaling WS] [werift PC] β
β β HELLO/SDP/ICE WebRTC termination β
β β β β
β β RTP packets β
β β β β
β β [ffmpeg pipe] β
β β RTSP publish βββββββββββββββ β
β β β β
β [AlarmEventListener] β β
β ADC WebSocket event stream β β
β motion / sensor / clip events β β
β β β β
βββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββΌβββββ
β motion webhook RTSP push β
β βββββββΌββββββ
β β go2rtc β (same container)
β β RTSP in β
β β RTSP out β
β βββββββ¬ββββββ
β β rtsp://localhost:8554/<cam>
β βββββββββββΌβββββββββββ
ββββββββββββββββββββββββββββββ homebridge-camera- β
β ffmpeg (HKSV) β
ββββββββββββββββββββββ
The ADC end-to-end WebRTC signaling protocol (ported from the HA integration's alarm-webrtc-card.js):
- Fetch video token:
GET /web/api/video/videoSources/liveVideoHighestResSources/<cameraId> - Extract
endToEndWebrtcConnectionInfofrom response (signalling URL, JWT token, camera auth token, ICE servers) - Connect WebSocket to
${signallingServerUrl}/${signallingServerToken} - Send
HELLO 2.0.1β receiveHELLO - Send
START_SESSION <cameraAuthToken>β receiveSESSION_STARTED - Receive SDP offer (JSON) β create answer with werift β send answer back
- Exchange ICE candidates
- WebRTC media flows (H.264 1080p @ 10fps)
The liveVideoHighestResSources API call triggers the camera to wake up and dial in to the signaling server. The camera takes a few seconds to connect, so:
- First attempt usually fails with "Camera has not yet dialed in"
- Retry with a fresh token after 15 seconds β the camera is now awake
- Subsequent retries use 10-second intervals
The bridge refreshes video tokens every 10 minutes, tearing down and re-establishing the WebRTC connection each time. ADC telemetry confirms there is no server-enforced session timeout β the refresh interval is set conservatively to keep signaling credentials fresh. Each rebuild causes a ~1-2 second gap in the RTSP stream.
Working:
- Alarm.com authentication via
node-alarm-dot-com - Camera discovery CLI (
npx tsx src/discover.ts) - Video token fetching and refresh (10-minute cycle)
- End-to-end WebRTC signaling (HELLO/START_SESSION/SDP/ICE)
- WebRTC connection establishment with STUN/TURN
- H.264 RTP packet extraction from werift
- H.264 fmtp passthrough (profile-level-id, sprop-parameter-sets from camera SDP offer)
- ffmpeg RTSP output to go2rtc
- Docker container with go2rtc sidecar
- Multi-camera streaming (3 cameras verified, 1920x1080 H.264 @ 10fps)
- Camera dial-in retry with exponential backoff (up to 12 attempts)
- Real-time motion detection via ADC WebSocket event stream
- WebSocket event listener with proactive token refresh and exponential backoff on errors
- Motion webhook forwarding to homebridge-camera-ffmpeg
- HomeKit live view and motion notifications via Homebridge
- HomeKit Secure Video (HKSV) recording triggered by motion events
Not yet done:
- go2rtc stream auto-configuration (currently manual in
config/go2rtc.yaml) - Audio passthrough
- Seamless stream handoff (overlap old/new streams during token refresh to eliminate gap)
src/
βββ index.ts # Entry point, graceful shutdown
βββ config.ts # YAML config loader
βββ types.ts # Shared interfaces
βββ discover.ts # Camera discovery CLI
βββ auth/
β βββ alarm-auth.ts # Wraps node-alarm-dot-com login + camera discovery
β βββ token-manager.ts # Session refresh (55min) + video token refresh (10min/camera)
βββ signaling/
β βββ signaling-client.ts # WebSocket: HELLO, START_SESSION, SDP/ICE relay
βββ camera/
β βββ camera-stream.ts # Per-camera: signaling β werift β RTP β ffmpeg β RTSP
β βββ camera-manager.ts # Multi-camera orchestration with backoff
βββ events/
β βββ alarm-event-listener.ts # ADC WebSocket event stream with proactive refresh
β βββ parse-event.ts # Event parsing (motion, sensor, clip events)
β βββ types.ts # Event type definitions
βββ go2rtc/
β βββ go2rtc-api.ts # go2rtc REST API health checks
βββ utils/
βββ logger.ts # pino structured logging
βββ retry.ts # Exponential backoff helper
βββ sdp.ts # H.264 fmtp extraction from SDP offers
See the Setup Guide for full end-to-end instructions covering Docker deployment, camera discovery, configuration, Homebridge integration, and HomeKit motion notifications.
Quick start:
git clone https://github.com/Omar-L/adc-video-bridge.git
cd adc-video-bridge
cp config/config.example.yaml config/config.yaml
cp config/go2rtc.example.yaml config/go2rtc.yaml
# Edit both config files with your credentials and camera IDs
docker compose -f docker-compose.yml up --build -dCredentials can be provided via config file or environment variables. Env vars are recommended for Docker deployments.
| Variable | Description | Required |
|---|---|---|
ADC_USERNAME |
Alarm.com account email | Yes |
ADC_PASSWORD |
Alarm.com account password | Yes |
ADC_MFA_TOKEN |
Two-factor authentication token (from trusted device setup) | No |
Config file values take precedence over env vars. See config/config.example.yaml for the full configuration reference.
- node-alarm-dot-com β Alarm.com authentication
- werift β Pure TypeScript WebRTC (server-side PeerConnection)
- ws β WebSocket client for ADC signaling
- go2rtc β RTSP server (accepts ffmpeg push, serves to clients)
- pino β Structured logging
- ffmpeg β RTP β RTSP transcoding (copy mode, no re-encoding)
ADC cameras are designed for on-demand live view, not continuous streaming. The bridge holds a perpetual live view session by refreshing tokens every 10 minutes, but this is a workaround β ADC does not officially support persistent streaming. ADC telemetry confirms there is no server-enforced WebRTC session timeout, so the 10-minute interval is conservative.
Alarm.com may ban accounts that poll too aggressively. Known safe minimums (from homebridge-node-alarm-dot-com):
- Session re-authentication: β₯10 minutes (bridge uses 55 min)
- Device polling: β₯60 seconds (bridge uses 10 min per camera)
With multiple cameras, aggregate API load scales linearly β 3 cameras means a video token API call roughly every 200 seconds.
Native HKSV via go2rtc (#11)
go2rtc PR AlexxIT/go2rtc#2130 adds native HomeKit Secure Video support with a standalone pkg/hksv/ library. This would eliminate the entire Homebridge stack β go2rtc would expose cameras directly to Apple Home with H.264 passthrough (no re-encoding), lower latency, and motion events via a simple HTTP API. See issue #11 for the full analysis and phased rollout plan.