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

webrtc-rs 0.21: A Sans-I/O Core with Runtime-Agnostic Async

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,145 stars508 forksRustApache-2.0

At a glance

What is it?
The webrtc crate splits WebRTC into a Sans-I/O protocol core and a thin async layer, letting you plug in Tokio, smol, or a custom runtime. This review covers the architecture, the pre-release state, and the trade-offs you need to check before adopting it.
Who is it for?
Adopt webrtc-rs if you need a Rust-native WebRTC stack that does not lock you into Tokio, and if you are willing to work with a pre-release API. Do not adopt it for a production service until you verify that the 0.21 API matches your use case, that your chosen crypto provider (ring or aws-lc-rs) meets your compliance needs, and that the runtime abstraction covers the timers and sockets you rely on.
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 11 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 September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

What Problem This Solves and Who It Is For

WebRTC in Rust has historically been a tangle of blocking I/O, runtime-specific code, and heavy dependencies. The webrtc crate aims to fix that by separating the protocol logic from the async runtime. The README describes the architecture as a Sans-I/O core called rtc, with a thin async layer on top. This split matters for engineers who build embedded systems, custom event loops, or multi-runtime services where forcing Tokio everywhere is not acceptable. The target audience is Rust developers who need WebRTC data channels and media, but who want to keep control over the async executor and the crypto backend. If you are happy to live inside Tokio's ecosystem, this crate still works, but its real value appears when you need to leave it.

The Sans-I/O Core and the Async Layer

The architecture is explicit in the README. The rtc crate holds the complete WebRTC protocol stack and claims 95%+ W3C API compliance. It does no I/O on its own. The webrtc crate adds an async layer with three pieces: PeerConnection, PeerConnectionDriver, and Runtime. PeerConnection is the user-facing handle; every operation like create offers, add tracks, and create data channels is async. PeerConnectionDriver is an internal event loop that owns sockets, drives the Sans-I/O core, handles timeouts, and dispatches events. It is spawned automatically when you build a connection. Runtime is a trait that abstracts timers, task spawning, and sockets. This is a Quinn-style abstraction, as the README notes. The design means the protocol state machine never blocks on network calls, and the async layer only schedules work. The trade-off is that you must trust the driver loop to manage your sockets correctly, because you do not see that code.

Getting It Running: Cargo Features and the Pre-Release Catch

To use the 0.21 pre-release, you must name the version in full. A plain "0.21" requirement will not select a pre-release, so the README shows webrtc = "0.21.0-alpha.1". The crate has two sets of additive features. Runtime features are runtime-tokio (default), runtime-smol, and runtime-mock. They are additive, meaning enabling several is safe and one process can drive different connections on different runtimes. Crypto features are crypto-ring (default) and crypto-aws-lc-rs. Building with neither compiles no provider, and you supply your own. The README gives a concrete example of selecting a provider via SettingEngineBuilder and with_crypto_provider. For a custom runtime, you implement the Runtime trait and pass it per connection with with_runtime. The custom-runtime example runs the full stack on async-executor plus async-io with --no-default-features, so neither Tokio nor smol is compiled in. That is the fastest way to validate your own runtime before committing.

Crypto Provider Selection and the FIPS Question

The crypto features are not just compile-time toggles. The README states that enabling both compiles both providers, and ring stays the default selection. This prevents a dependency from silently changing your crypto backend. You can also pass a provider per connection through SettingEngine, so two connections in one process can use different providers. For applications needing a FIPS-validated module, an HSM, or a platform backend, you implement crypto::RTCCryptoProvider and pass it the same way. The rtc-crypto conformance suite validates an implementation against the same RFC vectors the built-ins pass. The README is careful to say that no cryptography happens in the webrtc crate; it forwards the provider to rtc. This is a clean boundary, but it also means you are responsible for the correctness of any custom provider. If you need FIPS, verify that aws-lc-rs or your own provider actually meets the validation level you require, because the README does not claim any certification.

The PeerConnection Trait and Object Safety

The build() method returns an opaque impl PeerConnection. PeerConnection is an object-safe trait, so you can wrap it as Arc<dyn PeerConnection> to store it in a struct or share it across tasks. The README gives this exact pattern. The benefit is that no runtime or interceptor type parameters leak into your own types. This is a significant ergonomic improvement over earlier versions of the crate, where generic parameters could infect every struct that held a connection. The cost is that you lose some compile-time type information. For example, you cannot call methods that are not part of the PeerConnection trait without downcasting. In practice, this is a reasonable trade for the flexibility, but it is worth knowing before you design your application around it.

Limitations and Where It Is the Wrong Tool

The crate is in a pre-release state, with versions like 0.21.0-beta.2. That alone is a limitation for production use. The README does not mention stability guarantees, and the API may change between beta releases. Another limitation is that the runtime-mock feature is for tests only. It provides a deterministic virtual clock, but it does no I/O. If you need to test real network conditions, you must use a real runtime. The Sans-I/O core claims 95%+ W3C API compliance, but that is not 100%. Missing 5% could affect niche features like certain SDP attributes or legacy ICE modes. If your application depends on a rarely used WebRTC feature, you need to verify it against the conformance suite yourself. The crate is also the wrong tool if you need a synchronous API or if you cannot afford an extra event loop thread. The PeerConnectionDriver is spawned automatically, so every connection has a background task. In a resource-constrained environment, that overhead may be unacceptable.

Alternatives and the Difference in Approach

The most direct alternative is the original Pion stack in Go, which the README says inspired and largely rewrote this crate. Pion is not async in the Rust sense; it uses Go's goroutines and channels. The webrtc-rs project reimplements that design in Rust with a Sans-I/O core, which is a fundamentally different architecture. Another alternative is to use a C library like libwebrtc via FFI, which gives you the full native WebRTC implementation but ties you to its build system and threading model. The webrtc-rs approach avoids that by implementing the protocol in pure Rust, but it means you are trusting the rtc core's implementation of the RFCs. If you need battle-tested protocol behavior, libwebrtc has decades of deployment, while webrtc-rs is newer. The trade-off is control over the runtime and crypto versus maturity.

Maintenance and Upgrade Cost

The repository is not archived, and the last push is from 2026-08-22, with three releases in the same month. That indicates active development, but pre-release versions mean you should expect breaking changes. The README mentions 37 runnable examples, which is a good sign for keeping up with API changes. The license is Apache-2.0, which is permissive and allows commercial use. The dual MIT/Apache-2.0 license is common in the Rust ecosystem, but the README links to the Rust project's FAQ on that dual licensing. You should check the exact license text in the repository, as the README does not specify the dual licensing explicitly. Upgrade cost: because the API is still shifting, you should pin the exact version in your Cargo.toml and plan for migration work when a stable 0.21 or 1.0 lands. The runtime abstraction does isolate you from some changes, but the PeerConnection trait methods may still evolve.

Editorial conclusion

Adopt webrtc-rs if you need a Rust-native WebRTC stack that does not lock you into Tokio, and if you are willing to work with a pre-release API. Do not adopt it for a production service until you verify that the 0.21 API matches your use case, that your chosen crypto provider (ring or aws-lc-rs) meets your compliance needs, and that the runtime abstraction covers the timers and sockets you rely on. First, run the custom-runtime example with --no-default-features to confirm your runtime works, and check the rtc-crypto conformance suite before writing your own provider.

Official sources

  1. Official documentation
  2. Official README
  3. Project repository
  4. Release notes
Community notes

Community notes