Using the API
The API mirrors RTCPeerConnection, with one difference in shape: media arrives already encoded (video) or as PCM (audio), and events arrive through a single callback. The generated reference is under API reference.
Engine and peer
Create one NativeEngine per process. It owns a private tokio runtime, so the consumer needs no runtime of its own. Each create_peer call starts one driver task.
use qrtc::{native::NativeEngine, PeerEngine, PeerEvent, RtcConfiguration};
use std::sync::Arc;
let engine = NativeEngine::new()?;
let config: RtcConfiguration = serde_json::from_str(r#"{
"iceServers": [{ "urls": "turns:turn.example.org:443", "username": "u", "credential": "p" }],
"postQuantum": "prefer"
}"#)?;
let peer = engine.create_peer(&config, Arc::new(|event: PeerEvent| {
// Called on engine threads, in order for this peer connection.
}))?;
Every Peer method blocks until the driver answers. From async code, call them on a blocking pool such as tokio::task::spawn_blocking.
Configuration
RtcConfiguration deserializes from the JSON a page passes to new RTCPeerConnection(), with two additions.
| Field (JSON) | Meaning |
|---|---|
iceServers | STUN and TURN servers. urls is a string or an array; turn: with ?transport=tcp, and turns:, use a stream. |
iceTransportPolicy | "all" or "relay" |
bundlePolicy | "balanced", "max-compat" or "max-bundle" |
videoCodecs | Not W3C. Video codecs the consumer can encode and decode: "vp8", "h264". Absent means VP8 only. |
postQuantum | Not W3C. "off", "prefer" or "require"; see policies. Absent means default_pq_policy(). |
A connection, step by step
- Describe transceivers. Call
upsert_transceiverwith a wholeTransceiverSpeceach time one changes: id, kind, direction, stream ids, sender track id, whetheraddTrackmade it, codec preferences. The consumer picks ids belowREMOTE_TX_BASE(0x8000_0000); the engine numbers transceivers that a remote offer creates from there up. - Open data channels.
create_data_channelreturns aDataChannelInfowith an opaqueDcHandle. The SCTP id may still be unknown;PeerEvent::DcOpencarries it. - Negotiate.
create_offer,set_local_description, then send the SDP through your signalling. On the other side,set_remote_description,create_answer,set_local_description. Both setters return aTransceiverStateper transceiver: mid, current and remote direction, remote stream and track ids. Use them to firetrackandremovetrack. - Trickle candidates.
PeerEvent::IceCandidategives local candidates, thenNoneat the end. Remote ones go toadd_ice_candidate. - Send media. Push 48 kHz mono PCM with
push_pcmfor an audio sender; send encoded video withsend_frame. Received audio arrives asPeerEvent::AudioPcm, received video asPeerEvent::MediaFrame. - Close.
close()stops the driver and releases its sockets.
Events
PeerEvent serializes with a type tag named after the W3C event it drives. The media and message variants skip serialization: they carry bytes, so a consumer handles them directly.
| Variant | W3C counterpart |
|---|---|
IceCandidate | icecandidate |
IceGatheringStateChange, IceConnectionStateChange, ConnectionStateChange, SignalingStateChange | The matching *statechange events |
NegotiationNeeded | negotiationneeded |
DataChannel, DcOpen, DcClose, DcError, DcBufferedAmountLow, DcMessage | datachannel, and the channel's open, close, error, bufferedamountlow, message |
MediaFrame, AudioPcm | What a receiver's track carries |
KeyframeRequest, TargetBitrate | Encoder feedback, no event |
EncodedOut | Frames for an RTCRtpScriptTransform |
Audio controls
set_audio_processing(tx, echo_cancellation, noise_suppression, auto_gain_control)follows the track's constraints. Browsers process microphone tracks only.insert_dtmf(tx, event, duration_ms)sends one RFC 4733 tone. Events 0 to 15 are the digits,*,#and A to D.set_transform(tx, send, recv)diverts Opus frames through the consumer: outgoing ones asEncodedOut, to come back withsend_frame; incoming ones asMediaFrame, to come back withdecode_audio.
Errors
Every fallible method returns qrtc::Error. dom_name() gives the DOMException name to throw:
| Variant | DOMException | Typical cause |
|---|---|---|
InvalidState | InvalidStateError | Wrong signalling state, closed connection |
Operation | OperationError | The engine could not complete the step |
NotSupported | NotSupportedError | A post-quantum policy without a post-quantum feature |
Syntax | SyntaxError | Malformed SDP, candidate or ICE server URL |
InvalidModification | InvalidModificationError | A local description edited structurally |
Cargo features
| Feature | Default | Effect |
|---|---|---|
turn-tls-ring | yes | TURN over TLS with rustls on ring (compiles C and assembly) |
turn-tls-rustcrypto | no | TURN over TLS with rustls on RustCrypto; no C in the build |
pq-moduletto | yes | Post-quantum groups with moduletto ML-KEM, plus the ML-KEM-512 groups |
pq-hybrid | no | Post-quantum groups with RustCrypto ml-kem |
A fully pure Rust build: default-features = false, features = ["turn-tls-rustcrypto", "pq-hybrid"]. With no TLS feature, turns: servers are skipped. With no post-quantum feature, DTLS stays at 1.2.
A runnable peer
examples/stdio_peer.rs is a complete peer that speaks JSON lines on standard input and output. The Chromium interop test drives it; it is also the shortest full example of the API.
cargo run --example stdio_peer