# Video Path Selection (Forward RTSP vs Encoded Bytes) + AI-Lock Coordination

**Task**: AZ-676_telemetry_stream_video_path
**Name**: Operator-bound video path: forward RTSP URL OR carry encoded bytes; coordinate with frame_ingest ai_locked signal
**Description**: Two delivery modes for the operator video path (config-driven): (1) forward the RTSP URL to the operator (most common), (2) carry encoded bytes over the operator gRPC stream. Coordinate with `frame_ingest`'s `ai_locked` signal so AI inference is suppressed only while operator-led control occupies the frame budget.
**Complexity**: 3 points
**Dependencies**: AZ-640_initial_structure, AZ-657_frame_ingest_rtsp_session, AZ-675_telemetry_stream_grpc_server
**Component**: telemetry_stream
**Tracker**: AZ-676
**Epic**: AZ-637

## Problem

The operator sees the camera feed. Two modes are supported because some operator stacks attach to the RTSP source directly (lower onboard cost, recommended default), and others need bytes carried over the same operator-link channel (no separate RTSP socket to the operator).

When the operator-bound feed is active (either mode), `frame_ingest` MUST raise `ai_locked = true` so Tier-1 inference does not run on the same frames the operator is actively driving. The mechanism is a shared `Arc<AtomicBool>` (or equivalent) toggled by `telemetry_stream`'s session start/stop, read by `frame_ingest` (task 18) and `detection_client` (task 21).

## Outcome

- Config flag `video_path = "rtsp_forward" | "bytes_inline"`; default `rtsp_forward`.
- `rtsp_forward`: emit the canonical RTSP URL as part of the session-start telemetry.
- `bytes_inline`: take frames from `frame_ingest`'s broadcast channel and forward bytes to subscribed operator clients.
- `ai_locked` shared flag plumbed at startup; flipped to `true` while at least one operator session is consuming the video path, `false` otherwise.
- Health surface: `video_path_mode`, `ai_locked_state`, `bytes_inline_drops_total`.

## Scope

### Included
- Both modes (rtsp_forward + bytes_inline).
- ai_locked toggle wiring.
- Session-tracking (active client count gating ai_locked).

### Excluded
- RTSP server stream itself (it's owned by the camera; we just forward the URL).
- `frame_ingest` reading the flag (task 18 owns that).
- Snapshot/diff for MapObjects (task 38).

## Acceptance Criteria

**AC-1: rtsp_forward mode emits URL only**
Given `video_path = "rtsp_forward"` and a client subscribes
When the session starts
Then the client receives the configured RTSP URL in the session-start message; no bytes are streamed by this component.

**AC-2: bytes_inline forwards encoded frames**
Given `video_path = "bytes_inline"` and a client subscribes
When `frame_ingest` publishes 100 frames
Then the client receives all 100 (modulo bounded-queue drops handled by task 36).

**AC-3: ai_locked toggles on session start/stop**
Given no operator session is active (`ai_locked = false`)
When the first client subscribes to the video stream
Then `ai_locked` flips to `true`; when all clients disconnect, `ai_locked` flips back to `false`.

## Non-Functional Requirements

**Performance**
- bytes_inline: frame copy cost ≤2 ms p99 per frame on Jetson Orin Nano.
- AI-lock toggle latency: ≤50 ms from subscribe → flag flip.

## Runtime Completeness

- **Named capability**: operator video path (dual mode) + ai_locked coordination.
- **Production code that must exist**: both modes; real ai_locked atomic wired to consumers.
- **Unacceptable substitutes**: rtsp_forward that doesn't actually emit the URL (or bytes_inline that doesn't read frame_ingest) is unacceptable.
