For the complete documentation index, see llms.txt. This page is also available as Markdown.

WebRTC Streaming

Stream video to an Inference server over WebRTC and receive live predictions, using a model ID or a Workflow, from webcams, RTSP cameras, video files, or manually sent frames.

Use the inference-sdk WebRTC client to stream video through a model or Workflow. Video frames flow to an Inference Server over one connection, and processed frames plus prediction data flow back continuously.

The same client works with a self-hosted Inference Server and the Serverless Video Streaming API. Set api_url to the runtime you want:

  • Self-hosted: http://localhost:9001

  • Serverless: https://serverless.roboflow.com

WebRTC streaming requires extra dependencies:

pip install "inference-sdk[webrtc]"

Stream a model

Pass a model_id to stream video through one model. The SDK builds the required single-model Workflow, and your on_frame handler receives each video frame with its prediction data:

import cv2
import supervision as sv
from inference_sdk import InferenceHTTPClient
from inference_sdk.webrtc import WebcamSource

# Replace ROBOFLOW_API_KEY with your Roboflow API Key
client = InferenceHTTPClient(
    api_url="http://localhost:9001",
    api_key="ROBOFLOW_API_KEY",
)

session = client.webrtc.stream(
    source=WebcamSource(),
    model_id="rfdetr-nano",
)

box_annotator = sv.BoxAnnotator()

@session.on_frame
def show(frame, data):
    # data is the raw predictions dict, exactly as returned by the server
    # (None when predictions are unavailable for this frame)
    if data is None:
        return
    detections = sv.Detections.from_inference(data)
    annotated = box_annotator.annotate(frame.copy(), detections)
    cv2.imshow("Preview", annotated)
    if cv2.waitKey(1) & 0xFF == ord("q"):
        session.close()

session.run()  # blocks until the stream ends or session.close() is called

model_id works for any task type with a generic Workflow model block. The model's task type is resolved automatically via a Roboflow API lookup, and the matching model block is selected for you. Supported task types:

  • object-detection

  • instance-segmentation

  • semantic-segmentation

  • classification

  • multi-label-classification

  • keypoint-detection

data is the serialized predictions dict passed through verbatim - its shape follows the task type. For detection-family models it is inference-response-shaped, so you can convert it with the matching supervision helper - sv.Detections.from_inference(data) for object detection and instance segmentation, sv.KeyPoints.from_inference(data) for keypoint models. Classification predictions carry top/confidence keys, and semantic-segmentation predictions carry run-length-encoded masks (rle_mask) that you decode yourself.

When predictions are unavailable for a frame (e.g. the paired prediction message never arrived for a live stream frame), data is None - check for it in your handler before use.

VLMs are not supported in model_id mode (each VLM family has its own dedicated Workflow block, so there is no generic block to wrap them with) - stream them with a full workflow instead.

Skipping the task-type lookup: pass task_type explicitly to avoid the network call - useful for air-gapped or self-hosted deployments:

In model_id mode, on_frame handlers can take either (frame, data) or (frame, data, metadata) - the third argument is the VideoMetadata for the frame.

Stream a Workflow

For multi-step pipelines, pass a workflow instead of a model_id. Reference a Workflow saved in your Roboflow workspace by ID, or provide a full specification dict:

Notes:

  • workflow and model_id are mutually exclusive - pass exactly one.

  • workspace is required when workflow is an ID string; it is not needed for a specification dict.

  • image_input (default "image") names the Workflow image input the video frames are bound to.

  • In workflow mode, on_frame handlers receive (frame, metadata) - prediction data arrives separately through on_data handlers, routed by the data_output names in StreamConfig.

Video sources

The first argument to stream() selects where video comes from:

WebcamSource

Captures frames from a local camera device and sends them to the server:

The camera's FPS is auto-detected and reported to the server.

RTSPSource

The server connects to the RTSP camera and streams processed video back to you - use this when the camera is reachable from the server:

LocalStreamSource

Captures an RTSP/RTMP stream locally (on the client machine) and sends frames to the server - use this when the camera is only reachable from your machine, not from the server:

MJPEGSource

Like RTSPSource, but for MJPEG streams captured by the server:

VideoFileSource

Uploads a video file to the server over the data channel; the server processes it and streams results back. More efficient than frame-by-frame streaming for pre-recorded video:

By default frames come back through the data channel (guaranteed order and quality). Pass use_datachannel_frames=False to receive them via a hardware-accelerated WebRTC video track instead (lower bandwidth).

ManualSource

Send frames programmatically - useful when frames come from a custom pipeline:

send() raises RuntimeError until the connection is established, and queued frames are dropped oldest-first if you send faster than the stream is consumed. ManualSource has no FPS auto-detection, so declare the frame rate via StreamConfig(declared_fps=...).

Consuming results

The session lifecycle

stream() returns a WebRTCSession. The connection starts lazily on first use (run(), video(), or wait()) and must be closed to release resources. Three equivalent patterns:

session.close() is idempotent and safe to call from inside a handler - it ends run() and the video() iterator. session.wait(timeout=None) blocks until the stream ends without consuming frames yourself.

Receiving frames: on_frame and video()

@session.on_frame registers a handler invoked for every processed video frame when using run(). session.video() is the iterator equivalent - same data, pull-based:

Frames are BGR numpy arrays. If your handler falls behind in realtime mode, the oldest frames are dropped so the stream stays live.

Receiving data: on_data

Workflow outputs listed in StreamConfig.data_output arrive over the data channel. Register handlers per output name, or one global handler for the whole payload:

Handlers may accept (value, metadata) or just (value) - the signature is auto-detected.

Handling errors: on_error

The server reports per-frame errors (workflow execution failures, output serialization failures) alongside each data channel message. on_error handlers fire only for frames with a non-empty error list:

These are server-side per-frame failures; connection and setup errors surface as exceptions from run() instead. Errors are also attached to metadata.errors on every frame, so on_frame / on_data handlers can inspect them directly.

Frame metadata

VideoMetadata accompanies each frame and data message:

Attribute
Description

frame_id

Unique identifier of the frame in the stream

received_at

When the server received the frame

pts / time_base

Presentation timestamp of the video stream

declared_fps / measured_fps

Declared vs. measured stream FPS

errors

Per-frame errors reported by the server (empty when the frame processed cleanly)

StreamConfig

StreamConfig controls output routing, processing behavior, and network settings:

Field
Default
Description

stream_output

[]

Workflow output names streamed back as video

data_output

[]

Workflow output names delivered via the data channel

realtime_processing

True

Drop frames to keep up in real time; set False to queue and process every frame

declared_fps

None

FPS declaration for sources without auto-detection (e.g. ManualSource)

turn_server

None

TURN server config: {"urls": "turn:...", "username": "...", "credential": "..."}

workflow_parameters

{}

Parameters passed to the Workflow execution

requested_plan

None

Compute plan for Roboflow serverless endpoints (e.g. "webrtc-gpu-small")

requested_region

None

Processing region for serverless endpoints (e.g. "us", "eu")

processing_timeout

None

Server-side session time limit in seconds (serverless endpoints)

In model_id mode, empty stream_output / data_output are filled automatically (["image"] and ["predictions"]); any other settings you provide are preserved.

TURN servers: when connecting to Roboflow-hosted endpoints, TURN configuration is fetched automatically. For self-hosted servers behind restrictive NATs or firewalls, provide turn_server explicitly; direct connection is attempted when it is not set.

Runnable examples

Complete working scripts live in the examples/webrtc_sdk/ directory of the Inference repository:

You can stream against the Roboflow Serverless Hosted API (https://serverless.roboflow.com) with no setup, or against a local server for development:

Last updated

Was this helpful?