WebRTC Video chat tutorial using Rust+WASM

Crustacean over IP

Today we are going to walk through a simple video-chat application in Rust using WebRTC.

Scope of this tutorial

  • Rust to WASM code for 2 way video + audio call client in the browser
  • Seriously Simple Signalling Server


  • 2 WebRTC-capable Browsers
  • Some experience using Rust.

Longish Foreword

I also recommend using the serial debugging feature in chrome mobile to get the JS console output from your smartphone on your pc, this will help you see whats happening inside the browser.

We will be compiling Rust to WASM, If you are new to this space it may be worthwhile to read up on the Rust-WASM scene first, there are links at the bottom of this article.

We will not be using TURN or STUN servers, this is running on a local network and as long as both clients’ IP addresses can be reached then we wont need STUN/TURN.

This guide is meant to be read right along side the corresponding source code: repo-link. I will include snippets but I will mostly reference functions/types.



Basic WebRTC Setup

WebRTC Signalling Flow

  1. Peer B will be setup as a listener waiting for a Video Offer, Peer A will initiate sending the Video Offer to Peer B.
    Peer A immediately starts sending ICE Candidates to Peer B.
  2. Peer B gets the Video Offer and multiple of Peer A’s ICE Candidates.
    Peer B sets its remote SDP description equal to Peer A’s Video Offer.
  3. Peer B sends a Video Answer back to Peer A.
    Peer B immediately starts sending ICE Candidates to Peer A.
  4. Peer A gets the Video Answer, sets its remote SDP description equal to Peer B’s Video Answer.
  5. Multiple Ice Candidate’s are sent between the two peers until they have enough candidates to agree on a method of connecting to each other.
  6. Once they have agreed on a method to communicate, they add their own Media Streams to the connection, each Peer gets notified that the other Peer has added a Media stream to the connection. The RtcIceConnectionState changes to Connected, callbacks are triggered, the streams are output to a video element and video is transmitted between the peers.
WebRTC Flow

Lets get into the code

1. The Webpage

2. The client-side WASM

The RTCPeerConnection

The RTCPeerConnection is the main driver of the web RTC connection.
The main functions we care about are:

set_oniceconnectionstatechange : A function that takes a closure which handles what to do when the Ice Connection State changes
add_stream : This adds the remote stream to the local video element
set_onicecandidate : This adds the other peer’s ice candidate to the local peer’s list of ice candidates.


To handle signalling, we will be using an enum to wrap the setup messages then send them between client and server. We use serde to serialize/de-serialize the messages, so our enum must derive Serialize and Deserialize.
For signalling we have 4 different types of messages

VideoOffer(String, SessionID)
VideoAnswer(String, SessionID)
IceCandidate(String, SessionID)
ICEError(String, SessionID)

These are part of a larger enum SignalEnum later in this which we use to handle all communication between the client and the signalling-server.

Session Description Protocol (SDP)

There are 3 functions that are important with respect to SDP,
1. Creating the SDP offer and sending it from Peer A inside VideoOffer(String) to Peer B
2. Receiving the SDP offer at Peer B, setting Peer B’s remote description to the offer, and sending a SDP answer description back from Peer B inside a VideoAnswer(String)to Peer A
3. Receiving an answer at Peer A, and setting Peer A’s remote description to the answer.

Interactive Connectivity Establishment (ICE)
There are three things we are concerned about with ICE
1. Sending our candidates to the other peer
2. Receiving the other peers candidates
3. The ICE state change.

The Ice Connection state change to Connected is going to trigger our call to set the remote incoming video streams to display in our local html video element.
These are the possible states that ICE Connection State can be in.

Rtc Ice Connection State Enum from the web-sys crate

To compile the Rust client to wasm, from /wasm_client/ run cargo make build or cargo make watch if you plan on tinkering. This will output compiled binaries and some bootstrap js inside /wasm_client/pkg/ .

To host this webpage we can use the microserver crate by first installing it using cargo install microserver
Then by running cargo make serve in the root directory of the project.
Check the /wasm_client/Makefile.toml to see the command that is run.

3. The Signalling Server

The short version of how this works is: When a new client connects to the signalling server, they are assigned a random UserId, and are added to the UserList, and PeerMap application state.
When a client either starts listening for connections, or connects to another client that is listening, the client is placed in a SessionMembers struct, either as a host or a guest, inside the SessionList part of the application state.
When a client disconnects, they are removed from all of the above state, UserList, PeerMap and SessionList if they started/joined a session.

The reason we are wrapping all of the state in Arc<Mutex<>> ‘s is because async-tungstenite spawns a new task for each client new connection, and so we need our access to any state to be Send (Thread safe).

To build: from the /signalling-server/ directory run cargo make build
To start the server: run cargo make servesignal in the root directory of the project.
Check the /signalling-server/Makefile.toml to see the command that is run.

4. The Signalling Protocol


If you feel so, you can visit my buy me a coffee page, but there is no pressure from me. The great thing about this article is the sharing of free knowledge and some (hopefully) working code of a cool little project.
Happy coding and go make something great !

Further Useful links: