MCPcopy Create free account
hub / github.com/whitphx/streamlit-webrtc

github.com/whitphx/streamlit-webrtc @v0.75.0

Chat with this repo
repository ↗ · DeepWiki ↗ · release v0.75.0 ↗ · + Follow
580 symbols 2,286 edges 119 files 57 documented · 10% 1 cross-repo links updated 1d agov0.77.0 · 2026-08-07★ 1,70680 open issues

Browse by type

Functions 467 Types & classes 112 Endpoints 1
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

streamlit-webrtc

Handling and transmitting real-time video/audio streams over the network with Streamlit Open in Streamlit

Test and Build Post-build

PyPI PyPI - License PyPI - Downloads

Ruff

Sister project: streamlit-fesion to execute video filters on browsers with Wasm.

Examples

⚡️Showcase including following examples and more: 🎈Online demo

  • Object detection
  • OpenCV filter
  • Uni-directional video streaming
  • Audio processing

⚡️Real-time Speech-to-Text: 🎈Online demo

It converts your voice into text in real time. This app is self-contained; it does not depend on any external API.

⚡️Real-time video style transfer: 🎈Online demo

It applies a wide variety of style transfer filters to real-time video streams.

⚡️Video chat

(Online demo not available)

You can create video chat apps with ~100 lines of Python code.

⚡️Tokyo 2020 Pictogram: 🎈Online demo

MediaPipe is used for pose estimation.

Install

$ pip install -U streamlit-webrtc

Quick tutorial

See also the sample pages, pages/*.py, which contain a wide variety of usage.

See also "Developing Web-Based Real-Time Video/Audio Processing Apps Quickly with Streamlit".


Create app.py with the content below.

from streamlit_webrtc import webrtc_streamer

webrtc_streamer(key="sample")

Unlike other Streamlit components, webrtc_streamer() requires the key argument as a unique identifier. Set an arbitrary string to it.

Then run it with Streamlit and open http://localhost:8501/.

$ streamlit run app.py

You see the app view, so click the "START" button.

Then, video and audio streaming starts. If asked for permissions to access the camera and microphone, allow it. Basic example of streamlit-webrtc

Media toggle controls

When the app sends local camera or microphone input, webrtc_streamer() shows camera and microphone toggle buttons next to the Start/Stop button. These controls let users turn their outgoing camera or microphone track on and off without stopping the WebRTC session.

Set media_toggle_controls=False to hide these toggle buttons.

from streamlit_webrtc import webrtc_streamer

webrtc_streamer(key="example", media_toggle_controls=False)

When a user turns off the camera or microphone with these buttons, the WebRTC track stays active. As described in MDN's MediaStreamTrack.enabled documentation, disabled audio tracks send silence, and disabled video tracks send black frames; the session does not stop or renegotiate.

Next, edit app.py as below and run it again.

from streamlit_webrtc import webrtc_streamer
import av


def video_frame_callback(frame):
    img = frame.to_ndarray(format="bgr24")

    flipped = img[::-1,:,:]

    return av.VideoFrame.from_ndarray(flipped, format="bgr24")


webrtc_streamer(key="example", video_frame_callback=video_frame_callback)

Now the video is vertically flipped. Vertically flipping example

As an example above, you can edit the video frames by defining a callback that receives and returns a frame and passing it to the video_frame_callback argument (or audio_frame_callback for audio manipulation). The input and output frames are the instance of av.VideoFrame (or av.AudioFrame when dealing with audio) of PyAV library.

You can inject any kinds of image (or audio) processing inside the callback. See examples above for more applications.

Pass parameters to the callback

You can also pass parameters to the callback.

In the example below, a boolean flip flag is used to turn on/off the image flipping.

import streamlit as st
from streamlit_webrtc import webrtc_streamer
import av


flip = st.checkbox("Flip")


def video_frame_callback(frame):
    img = frame.to_ndarray(format="bgr24")

    flipped = img[::-1,:,:] if flip else img

    return av.VideoFrame.from_ndarray(flipped, format="bgr24")


webrtc_streamer(key="example", video_frame_callback=video_frame_callback)

Pull values from the callback

Sometimes we want to read the values generated in the callback from the outer scope.

Note that the callback is executed in a forked thread running independently of the main script, so we have to take care of the following points and need some tricks for implementation like the example below (See also the section below for some limitations in the callback due to multi-threading).

  • Thread-safety
  • Passing the values between inside and outside the callback must be thread-safe.
  • Using a loop to poll the values
  • During media streaming, while the callback continues to be called, the main script execution stops at the bottom as usual. So we need to use a loop to keep the main script running and get the values from the callback in the outer scope.

The following example is to pass the image frames from the callback to the outer scope and continuously process it in the loop. In this example, a simple image analysis (calculating the histogram like this OpenCV tutorial) is done on the image frames.

threading.Lock is one standard way to control variable accesses across threads. A dict object img_container here is a mutable container shared by the callback and the outer scope and the lock object is used at assigning and reading the values to/from the container for thread-safety.

import threading

import cv2
import streamlit as st
from matplotlib import pyplot as plt

from streamlit_webrtc import webrtc_streamer

lock = threading.Lock()
img_container = {"img": None}


def video_frame_callback(frame):
    img = frame.to_ndarray(format="bgr24")
    with lock:
        img_container["img"] = img

    return frame


ctx = webrtc_streamer(key="example", video_frame_callback=video_frame_callback)

fig_place = st.empty()
fig, ax = plt.subplots(1, 1)

while ctx.state.playing:
    with lock:
        img = img_container["img"]
    if img is None:
        continue
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    ax.cla()
    ax.hist(gray.ravel(), 256, [0, 256])
    fig_place.pyplot(fig)

Callback limitations

The callbacks are executed in forked threads different from the main one, so there are some limitations: * Streamlit methods (st.* such as st.write()) do not work inside the callbacks. * Variables inside the callbacks cannot be directly referred to from the outside. * The global keyword does not work expectedly in the callbacks. * You have to care about thread-safety when accessing the same objects both from outside and inside the callbacks as stated in the section above.

Cleanup on Stop (session lifecycle)

webrtc_streamer() accepts on_video_ended and on_audio_ended arguments — zero-argument callables that fire when the corresponding input media track ends (the user clicks "STOP", closes the page, or the connection drops). They are the recommended hook for tearing down per-session resources that the frame callbacks allocated, such as worker threads, model handles, file writers, queues, or st.session_state entries.

import streamlit as st
from streamlit_webrtc import webrtc_streamer


def video_frame_callback(frame):
    # ... process the frame, possibly initializing per-session state on first call ...
    return frame


def on_video_ended():
    st.session_state.pop("my_session_resource", None)


webrtc_streamer(
    key="example",
    video_frame_callback=video_frame_callback,
    on_video_ended=on_video_ended,
)

These callbacks run on aiortc's asyncio loop — not Streamlit's main thread — so the same caveats as the frame callbacks apply: st.* calls do not work inside them, and shared state must be mutated in a thread-safe way (e.g. with a threading.Lock, a queue, or a threading.Event).

When using the class-based API, override VideoProcessorBase.on_ended() / AudioProcessorBase.on_ended() instead — they fire at the same lifecycle point.

Source/sink track lifecycle

Factory helpers such as create_video_source_track(), create_audio_source_track(), create_video_sink_track(), create_audio_sink_track(), and create_pcm_audio_source_track() cache their returned objects in st.session_state by key so they survive Streamlit reruns. This is usually what you want: widget changes and reruns keep using the same media track.

By default, factory-created source and sink tracks are scoped to the active WebRTC session. When that session ends, for example when the user clicks STOP, closes the page, or the connection drops, the cached object is stopped and removed from st.session_state. The next WebRTC session with the same key gets a fresh object.

from streamlit_webrtc import create_video_source_track

video_track = create_video_source_track(
    callback=video_source_callback,
    key="video-source",
)

To keep a factory-created object alive across multiple WebRTC sessions in the same Streamlit session, opt out with lifecycle_scope="streamlit-session":

video_track = create_video_source_track(
    callback=video_source_callback,
    key="video-source",
    lifecycle_scope="streamlit-session",
)

lifecycle_scope applies to source/sink factory helpers and create_pcm_audio_source_track(). It does not affect webrtc_streamer() itself, create_process_track(), or create_mix_track(), whose lifecycles are tied to input tracks or explicit mixer reuse.

Class-based callbacks

The function-based callbacks (video_frame_callback / audio_frame_callback) shown above are the recommended API.

Until v0.37, the class-based callbacks (video_processor_factory / audio_processor_factory taking a VideoProcessorBase / AudioProcessorBase subclass) were the standard. They are still supported for backward compatibility — see the old version of the README for that style — but new code should prefer the function-based callbacks. The class-based API is planned to be removed in a future major release (v1.0).

Serving from remote host

When deploying apps to remote servers, there are some things you need to be aware of. In short, * HTTPS is required to access local media devices. * STUN/TURN servers are required to establish the media stream connection.

See the following sections.

HTTPS

streamlit-webrtc uses getUserMedia() API to access local media devices, and this method does not work in an insecure context.

This document says [^1]

A secure context is, in short, a page loaded using HTTPS or the file:/// URL scheme, or a page loaded from localhost.

[^1]: For more details about This document explains the concept of secure context in detail.

So, when hosting your app on a remote server, it must be served via HTTPS if your app is using webcam or microphone. If not, you will encounter an error when starting using the device. For example, it's something like below on Chrome.

Error: navigator.mediaDevices is undefined. It

Extension points exported contracts — how you extend this code

browse all types & interfaces →

Core symbols most depended-on inside this repo

browse all functions →

Shape

Function 269
Method 198
Class 75
Interface 37
Route 1

Languages

Python83%
TypeScript17%

Modules by API surface

streamlit_webrtc/webrtc.py37 symbols
tests/process_test.py31 symbols
tests/factory_lifecycle_scope_test.py28 symbols
tests/webrtc_loopback_test.py27 symbols
streamlit_webrtc/component.py25 symbols
tests/component_pure_test.py22 symbols
streamlit_webrtc/models.py20 symbols
tests/shutdown_test.py18 symbols
tests/models_test.py18 symbols
streamlit_webrtc/sink.py18 symbols
streamlit_webrtc/process.py18 symbols
streamlit_webrtc/config.py18 symbols

Used by 1 indexed graphs manifest dependencies, hub-wide

For agents

$ claude mcp add streamlit-webrtc \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page