Library / SDK
webrtc-rs/webrtc avatar
webrtc-rs/webrtc

webrtc-rs/webrtc: a runtime-agnostic WebRTC stack in Rust

Project brief: Async-friendly WebRTC implementation in Rust. The async webrtc crate is a clean, ergonomic, runtime-agnostic rewrite on top of a Sans-I/O core; it ships with Tokio and smol runtime backends, and any other runtime can be plugged in by implementing one trait.

5,156 stars518 forksRustApache-2.0

At a glance

What is it?
The async webrtc crate is a thin layer over a Sans-I/O protocol core, with Tokio and smol backends and a per-connection Runtime trait. It suits Rust services that need WebRTC without inheriting a particular executor, and it is still on 0.21 pre-releases.
Who is it for?
Adopt it if you are writing a Rust service that must speak WebRTC and you refuse to let Tokio or smol dictate your architecture: the Runtime trait, the additive feature flags and the per-connection crypto provider are the reason to pick this over the alternatives.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 5 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 1, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What webrtc-rs/webrtc is for, and who should reach for it

WebRTC is a browser technology first. Servers that need to terminate or originate WebRTC sessions usually end up either embedding a browser engine or binding to a C++ stack. webrtc-rs/webrtc exists for the second group: people writing Rust services that must create offers, answer them, carry media and data channels, and do it inside the same process as the rest of their application.

The README describes the project as "originally inspired by and largely rewriting the Pion stack", which tells you the intended audience. If you already run Go media servers, this is the same shape of component in a different language. The crate targets network programming, and Cargo.toml lists it under the networking and protocols categories, so the expected user is a backend engineer, not someone building a browser page.

The distinguishing claim is runtime independence. Most async Rust networking libraries assume Tokio. This one does not, and that assumption is the whole design. If your service already runs on Tokio or smol, the benefit is small. If you have an executor of your own, or you are embedding WebRTC into a larger runtime with its own reactor, the benefit is the reason to look here at all.

PeerConnection, PeerConnectionDriver and the Sans-I/O core

The architecture is split into two repositories. The rtc crate is the Sans-I/O protocol core, described in the README as having "complete WebRTC stack (95%+ W3C API compliance)". Sans-I/O means it does not touch sockets or clocks itself. It consumes input and produces output, and something else performs the I/O.

The webrtc crate is that something else. It is a thin async layer over rtc and exposes three pieces. PeerConnection is the user-facing handle, and the README states that all operations (create offers and answers, add tracks, create data channels) are async. PeerConnectionDriver is an internal background event loop, spawned automatically, that owns the sockets, drives the Sans-I/O core, handles timeouts, and dispatches events. Runtime is a trait abstracting timers, task spawning, and sockets.

That division matters when something goes wrong. A Sans-I/O core is deterministic given the same inputs, so protocol bugs reproduce in tests without a network. The driver is where timing and I/O live, and it is the part that differs between runtimes. The README points to a runtime-mock feature that gives tests a deterministic virtual clock, so timing behaviour can be exercised without real time passing.

The event model is trait-based. You implement PeerConnectionEventHandler and receive callbacks such as on_ice_candidate. The README shows build() returning an opaque impl PeerConnection, and notes that PeerConnection is an object-safe trait, so you wrap it in Arc<dyn PeerConnection> when you need to store it or share it across tasks. The stated payoff is that no runtime or interceptor type parameters leak into your own types.

Installing webrtc and creating your first peer connection

Add the crate to Cargo.toml. The README warns that Cargo does not select pre-release versions from a plain "0.21" requirement, so the version must be named in full. The README gives this example, and the published pre-releases follow the same pattern:

toml
[dependencies]
webrtc = "0.21.0-alpha.1"

After adding it, run cargo build. With default features you get runtime-tokio and crypto-ring, which is enough for a Tokio application with the ring crypto provider. If you want smol instead, or the aws-lc-rs provider, those are separate features rather than replacements.

The README documents the feature flags in a table: runtime-tokio and crypto-ring are default; runtime-smol, runtime-mock and crypto-aws-lc-rs are not. The runtime features are additive, so enabling several is safe and a single process can drive different connections on different runtimes. The crypto features behave the same way, and ring stays the default selection even when both are compiled.

A first connection needs a handler and a builder. The README shows the handler trait and the start of the builder chain:

rust
use std::sync::Arc;
use webrtc::peer_connection::{
    PeerConnection, PeerConnectionBuilder, PeerConnectionEventHandler,
    RTCConfigurationBuilder, RTCIceServer, RTCPeerConnectionIceEvent, SettingEngineBuilder, crypto,
};
use webrtc::runtime::TokioRuntime;

#[derive(Clone)]
struct MyHandler;

#[async_trait::async_trait]
impl PeerConnectionEventHandler for MyHandler {
    async fn on_ice_candidate(&self, event: RTCPeerConnectionIceEvent) {
        println!("New local ICE candidate gathered: {}", event.candidate);
    }
}

What you should see once the connection is built and gathering begins is the on_ice_candidate callback firing with local candidates. For a complete flow, the README points at 37 runnable examples covering data channels, media playback, simulcast, ICE restart and insertable streams. Starting from examples/data-channels-simple is more useful than assembling the builder chain from the API docs, because the examples show the full sequence including the driver being spawned.

If you want a runtime the crate does not ship, the README states the built-ins are not privileged: implement webrtc::runtime::Runtime and pass it per connection with with_runtime, with no #[cfg] edits and no fork. The custom-runtime example runs the full stack on async-executor and async-io with --no-default-features.

The runtime and crypto flags are additive, and that is a deliberate constraint

Most Rust crates treat features as a choice: enabling one disables another, and the last crate in the dependency graph to set a feature wins. That model breaks badly for a library whose behaviour depends on which executor is compiled in, because an unrelated dependency can silently switch your runtime.

The Cargo.toml comments state the opposite policy. Runtime features "never select which primitives the library uses", so enabling both is safe and a process may drive connections on either. Crypto features forward to rtc and are additive, and the comment is explicit that a dependency turning on crypto-aws-lc-rs cannot silently change which provider your application runs.

This is a real design decision with a cost. Additive features mean the crate compiles more code than any single application needs, and there is no way to assert at compile time that your chosen runtime is the only one present. The payoff is that feature unification cannot change behaviour behind your back, which is the failure mode that makes runtime selection in Rust libraries unpleasant.

The crypto side has a second layer. The README states that no cryptography happens in this crate; it forwards the provider to rtc. A --no-default-features build compiles no built-in provider and requires the application to supply one through SettingEngine::set_crypto_provider. Applications needing a FIPS-validated module, an HSM, or a platform backend implement crypto::RTCCryptoProvider and pass it the same way. The README also notes that rtc-crypto's conformance suite validates an implementation against the same RFC vectors the built-ins pass. If you are considering writing your own provider, that conformance suite is the thing to look at first, since it defines what "correct" means here.

Where this is the wrong choice

The first limitation is version state. Cargo.toml declares version 0.21.0-rc.2, and the recent releases are v0.21.0-beta.2, v0.21.0-beta.1 and v0.21.0-alpha.2. Anyone who needs a stable, non-pre-release dependency should not build on this today. The README's own getting-started section is addressed to people "trying the 0.21 pre-release", which is a fair summary of the current position. The last push to the repository was on 2026-08-22.

The second limitation is language reach. This is a Rust crate. The related searches around WebRTC Flutter, WebRTC Chrome and React point at a different audience entirely, and nothing in the repository serves them. If your product is a mobile client or a browser page, the WebRTC implementation you need is already in the platform, and this crate is not a substitute for it.

The third is the Sans-I/O split itself. It buys testability and runtime independence, but it means the crate is not a drop-in for code written against a socket-owning API. You do not hand it a UDP socket; the driver owns the sockets. That is a constraint on how you structure shutdown, resource limits and observability, and it is not something the README addresses.

Finally, the documentation is uneven. The README covers architecture, features and the start of a builder chain, and it points at the API docs and the examples. It does not document rollback, version migration between the 0.21 pre-releases, or what changed in the alpha to beta to rc sequence beyond CHANGELOG.md. For a crate at this stage, expect to read source and examples rather than prose.

How this differs from Pion and from browser-side WebRTC

The nearest comparison is Pion, and the README makes the relationship explicit: the project was "originally inspired by and largely rewriting the Pion stack". Both are Sans-I/O-oriented WebRTC stacks for server-side use, and both expose a peer connection abstraction. The difference is the language and what follows from it. Pion is Go, so it inherits goroutines and the Go scheduler; there is no runtime trait because there is nothing to abstract over. This crate is Rust, and the Runtime trait exists precisely because Rust has no single executor.

The second alternative is not a library at all. If your WebRTC endpoints are browsers, the implementation is in the browser and your server only needs signalling. In that arrangement the server exchanges SDP and ICE candidates and never terminates media. Choosing this crate means taking on media termination, codec handling and the associated operational surface, which is a much larger commitment than running a signalling endpoint.

The third alternative is binding to a C++ WebRTC implementation. That gets you the reference stack, but it also gets you a C++ build and a foreign-function boundary in the middle of your async code. The trade is maturity against integration cost, and the choice depends on whether your team would rather debug Rust or a build system.

Licence, upgrades and what maintenance looks like

The crate is dual-licensed. Cargo.toml declares license = "MIT/Apache-2.0", and the repository root carries both LICENSE-APACHE and LICENSE-MIT. The README's badge links to the Rust project's explanation of dual MIT/Apache-2.0 licensing. Dual licensing under those two terms is the Rust ecosystem norm, and it means downstream users pick one. This is a description of what the repository states, not legal advice; if your organisation has specific obligations around attribution or patent terms, the two licence files are the ones to read.

Upgrade cost is dominated by the pre-release cadence. Three releases landed in August 2026: alpha.2 on 2026-08-16, beta.1 on 2026-08-19 and beta.2 on 2026-08-22. The README's instruction to name the version in full, because Cargo will not select a pre-release from "0.21", means every upgrade is an explicit edit to Cargo.toml rather than a range bump. That is inconvenient, and it is also a safeguard: you cannot drift into a new pre-release by accident.

The rtc dependency is pinned by both version and path, rtc = { version = "0.21.0-rc.2", path = "rtc", default-features = false }, so the async layer and the core move together. Because the crypto features forward to rtc, a change in the core's provider story surfaces here as a feature change rather than a code change. CHANGELOG.md at the repository root is where the release notes live; the README does not summarise them, so it is the file to read before bumping the version string.

Editorial conclusion

Adopt it if you are writing a Rust service that must speak WebRTC and you refuse to let Tokio or smol dictate your architecture: the Runtime trait, the additive feature flags and the per-connection crypto provider are the reason to pick this over the alternatives. Do not adopt it if you want a stable published version, since Cargo.toml carries 0.21.0-rc.2 and the recent releases are pre-releases; do not adopt it if you need a maintained non-Rust binding, because this repository is Rust only. Before you commit, read examples/custom-runtime and confirm that your own executor can satisfy the Runtime trait, then decide whether crypto-ring or crypto-aws-lc-rs matches your deployment. Verify the licence split between LICENSE-APACHE and LICENSE-MIT before you ship.

Frequently asked questions

What is webrtc-rs/webrtc used for?

It is an async-friendly WebRTC implementation in Rust, used by server-side applications that need to create offers and answers, add tracks and create data channels in the same process as the rest of a Rust service. The README describes it as originally inspired by and largely rewriting the Pion stack.

Is webrtc-rs/webrtc better than WebSockets?

The repository does not compare itself to WebSockets, so there is no project-specific answer. What the README does state is that the crate implements the WebRTC stack on top of a Sans-I/O core with 95%+ W3C API compliance, which is a different scope from a message transport.

How do I install webrtc-rs/webrtc?

Add it to Cargo.toml. The README warns that Cargo does not select pre-release versions from a plain "0.21" requirement, so the version must be named in full, and it gives webrtc = "0.21.0-alpha.1" as the example.

How do I use webrtc-rs/webrtc?

Implement the PeerConnectionEventHandler trait, then build a connection with PeerConnectionBuilder. All operations on PeerConnection are async, and a PeerConnectionDriver is spawned automatically to own the sockets and dispatch events.

Official sources

  1. Official documentation
  2. Official README
  3. Project repository
  4. Release notes
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/webrtc-rs-webrtc.svg)](https://hysenlabs.com/projects/webrtc-rs-webrtc)