A Python client that streams video from a camera to a remote receiver (intercom/monitor) using WebRTC, authenticated via OAuth 2.0 Device Authorization Grant.
- OAuth 2.0 Device Code Grant (
urn:ietf:params:oauth:grant-type:device_code) initiate_device_authorization()— POSTs to/oauth/device-authorization/with device type/OS, receivesdevice_code+user_codefor user approvalpoll_for_token()— Polls/oauth/token/with the device code until user approves (times out after 5 minutes)refresh_tokens()— Refreshes access token using stored refresh tokenTokenStore— Securely stores access/refresh tokens to a JSON file (permissions0600) at~/.config/intercomclient/tokens.json
- Connects to WebSocket signaling server at
{WEBSOCKET_API_BASE_URL}/ws/live_stream/{device_code}/ - Authenticates with Bearer token header
- Signaling loop handles:
offer— Sets remote SDP, createsCameraVideoStreamTrack, generates SDP answer, sends it backice— Receives ICE candidates from remote peer via signaling servericecandidateevents — Sends locally-generated ICE candidates back through signaling server
- Handles connection lifecycle: shutdown on
failed/closed, retry on errors with 5s backoff
CameraVideoStreamTrackextendsaiortc.VideoStreamTrack- Uses OpenCV (
cv2.VideoCapture) to capture frames from camera source (default device0) - Converts frames to
av.VideoFrame(BGR24 format) with proper PTS/time_base timestamps - Retries silently on capture failure
- All configurable via environment variables:
HTTP_API_BASE_URL,WEBSOCKET_API_BASE_URL,VIDEO_SOURCE,TOKEN_FILE_PATH,OAUTH_CLIENT_ID,OAUTH_CLIENT_SECRET - OAuth credentials can come from
~/.config/intercom-api/oauth.jsonor env vars - Defaults: 320×240 resolution, 5 fps, XVID codec
- Creates
PiClient, registers SIGINT/SIGTERM handlers, runs client in a loop ensuring valid tokens and maintaining the WebRTC connection
Never modify .envrc. It contains local-only developer environment config (API URLs, credentials, etc.) and must always reflect the developer's own setup. It is not part of the shared codebase and should never be changed by Claude.