WebRTC interop adapter for rvoip-core: terminates foreign WebRTC peers
(ICE/DTLS-SRTP, SDP offer/answer) and exposes voip-3 Connections with channel-based
MediaStream flows.
Built on webrtc-rs 0.20.0-alpha.1 (Sans-I/O rtc core + async
PeerConnectionBuilder / PeerConnectionEventHandler API).
- Dual role: gateway/interop adapter (
WebRtcAdapter→ orchestrator) and WebRTC server (WHIP/WHEP/WS signaling surfaces feeding the same adapter). Seedocs/archived/IMPLEMENTATION_PLAN.md§1. - In scope: WHIP/WHEP and WebSocket JSON signaling, 1:1 audio + VP8 video interop,
full-gather and trickle ICE, Opus + G.711, SCTP data channels, RFC 4733 DTMF
send/receive, fixture-encoded RTP for deterministic tests,
ConnectionAdapterforTransport::WebRtc, the QUIC bridge demo/test, and external TURN configuration viaIceServerConfig. - Out of scope / beta gaps: UCTP substrate (see
rvoip-websocket), multi-party SFU/MCU, simulcast/SVC, hosted TURN relay operation, identity fingerprint binding. SeeWebRtcFeatureSupportandtests/webrtc_capability_gaps.rs.
Initial RFC 4733 negotiation is qualified for the explicitly registered
PT 101/8 kHz, PT 110/48 kHz, and PT 126/8 kHz mappings. The current
rtc/webrtc-rs alpha retains an established payload mapping when a later
offer remaps telephone-event on the same media section. If the final answer
omits the newly offered pair, rvoip marks outbound DTMF unsupported and fails
before writing RTP; it never guesses or silently reuses the old payload type.
Supporting mid-session PT remapping is a candidate for the reviewed private
fork, not a claimed capability. Do not publish or submit such a fork change
upstream without project-owner review.
| Feature | Enables |
|---|---|
signaling-whip |
WHIP/WHEP HTTP endpoints (signaling::whip) |
signaling-ws |
WebSocket JSON SDP signaler |
client |
Native WebRtcClient surface |
comprehensive |
client + WS signaling + full WebRTC basics E2E (bidirectional audio/VP8, fixture RTP, SCTP DC chat, DTMF, gap tests) |
bridge-quic |
Real rvoip-quic cross-transport bridge demo + e2e test |
Enable both signaling features for the unified WebRtcServer facade.
Dual-role deployment: one process runs WHIP/WHEP + WS signaling and registers the same
WebRtcAdapter with rvoip_core::Orchestrator.
use std::sync::Arc;
use rvoip_core::adapter::ConnectionAdapter;
use rvoip_core::config::Config;
use rvoip_core::orchestrator::Orchestrator;
use rvoip_webrtc::{WebRtcConfig, WebRtcServerBuilder};
let server = WebRtcServerBuilder::new(WebRtcConfig::default())
.with_whip("0.0.0.0:8080")
.with_ws("0.0.0.0:8081")
.build()
.await?;
let orchestrator = Orchestrator::new(Config::default());
orchestrator.register(server.adapter() as Arc<dyn ConnectionAdapter>)?;
// Subscribe to orchestrator events; on ConnectionInbound call
// orchestrator.route_inbound_connection(..., InboundAction::Accept { ... })For attachment-token or other durable routing policies, enable fail-closed protocol admission before listeners start and install the orchestrator's single-consumer gate before registering the adapter:
use std::time::Duration;
let orchestrator = Orchestrator::new(Config::default());
let mut admissions = orchestrator.install_inbound_admission_gate(
256,
Duration::from_secs(5),
)?;
let server = WebRtcServerBuilder::new(WebRtcConfig::default())
.with_inbound_admission_confirmation(Duration::from_secs(5))
.with_whip_auth(my_auth_hook.clone())
.with_ws_auth(my_auth_hook)
.with_whip("0.0.0.0:8080")
.with_ws("0.0.0.0:8081")
.build()
.await?;
orchestrator.register(server.adapter() as Arc<dyn ConnectionAdapter>)?;
// A bounded policy task consumes `admissions` and calls `accept()` only
// after durable authorization; every other path rejects or drops the ticket.Secure mode requires authentication hooks to return a complete active
principal (issuer, tenant, subject, and non-anonymous assurance) plus an
inbound routing hint. WHIP uses its path tag as the hint; WebSocket hooks set
AuthContext::session_hint. WHEP is outbound and is not held by this gate.
Quick start:
./scripts/demo-webrtc-server.sh
# or
cargo run -p rvoip-webrtc --example webrtc_server --features signaling-whip,signaling-wsWHIP publish → orchestrator → synthetic QUIC leg (frame pump). Lightweight stand-in before wiring real adapters:
./scripts/demo-webrtc-bridge.sh
# or
cargo run -p rvoip-webrtc --example webrtc_bridge_demo --features signaling-whipIntegration test: cargo test -p rvoip-webrtc --features signaling-whip --test webrtc_bridge_e2e
WHIP publish → orchestrator → rvoip-quic::UctpQuicAdapter (auth + session.invite +
datagram media):
./scripts/demo-webrtc-quic-bridge.sh
# or
cargo run -p rvoip-webrtc --example webrtc_quic_bridge_demo --features bridge-quicIntegration test:
cargo test -p rvoip-webrtc --features bridge-quic --test webrtc_quic_bridge_e2eFor a full multi-adapter stack (QUIC + WT + WS + SIP), see
rvoip-uctp/examples/uctp_to_sip_bridge/orchestrator_bridge.rs.
Environment variables: WHIP_BIND (default 127.0.0.1:8080), WS_BIND (default 127.0.0.1:8081), QUIC_BIND (default 127.0.0.1:4433).
Exercises WebRtcClient against WebRtcServer over
WebSocket signaling — SDP (m=audio, m=video), full-gather ICE, ICE/DTLS connect, SCTP
data-channel ping/pong + arbitrary chat echo (RFC 8831), fixture-encoded Opus/VP8 RTP bursts
(server→client and client→server video), RFC 4733 DTMF, and server-side remote-track
confirmation via stats JSON.
Optional env: CHAT_MESSAGE (custom chat body), MEDIUM (audio|video|audiovideo).
./scripts/test-webrtc-comprehensive.sh
# or separately:
cargo run -p rvoip-webrtc --example webrtc_comprehensive_server --features comprehensive
WS_URL=ws://127.0.0.1:8081 CHAT_MESSAGE="Hello team" \
cargo run -p rvoip-webrtc --example webrtc_comprehensive_client --features comprehensive -- audiovideoIntegration tests:
cargo test -p rvoip-webrtc --features comprehensiveCapability tests and non-claim gap tests (trickle ICE, simulcast, TURN config, WS signaling):
tests/webrtc_capability_gaps.rs.
WebRtcOriginateContext::require_remote_admission_ready() enables a fail-closed,
application-level readiness boundary for outbound WS/WSS calls. The client sends
offer-ready instead of the legacy offer; after returning the SDP answer, the
server sends ready only after ConnectionAdapter::accept completes, or rejected
when the application rejects the exact route. Both outcomes carry the original
request_id and the server-assigned connection_id, and the client validates both
before changing lifecycle state.
The extension is deliberately default-off. Plain offer receives no readiness
frames, so older clients remain compatible with a new server. A required client
fails closed against an older server because that server rejects the unknown
offer-ready message; rvoip never silently falls back to answer-based activation.
| Type | Methods |
|---|---|
WebRtcServerBuilder |
new, with_whip, with_ws, with_inbound_admission_confirmation, build |
WebRtcServer |
adapter, whip_addr, ws_addr, shutdown |
This crate is a 1:1 WebRTC gateway/server adapter. It deliberately does not implement SFU/MCU fan-out — every connection is an independent peer.
- WHEP routing is one-connection-per-subscriber.
POST /whep/{tag}creates a freshPeerConnectionper subscriber. The crate does not share a single ingest publisher across multiple subscribers — eachwhep_postallocates its ownconnection_idand answers with its own SDP. Usemediasoup,Galène, or LiveKit when you need one-to-many media fan-out. - No simulcast layer selection. Simulcast offers are detected (see
sdp_indicates_simulcast()) but not forwarded — there is no layer-picking logic because there is no fan-out. - No mixing / MCU. Multi-party audio mixing belongs in a media server layered on top.
- No identity fingerprint binding claim. DTLS fingerprint pinning hooks exist, but binding them to rvoip identity assurance is not part of the beta claim.
See docs/GAP_PLAN.md §4 for the complete
out-of-scope list.
rvoip-websocket may replace its stub WebRtcMediaBridge with types
from this crate in a follow-up PR — WebRTC expertise stays here.
See docs/archived/IMPLEMENTATION_PLAN.md for the full design.