A full-mesh WebRTC swarm built on top of WebSockets & fast-rtc-peer
yarn add @mattkrick/fast-rtc-swarm
fast-rtc-peer offers a great API to connect 2 peers. If you'd like to connect more than 2 peers, you're on your own. That's why this exists. It uses a full mesh (vs. partial mesh) network so every client is connected to every other client. A full mesh is great for up to ~100 connections. After that, you'll probably want to move to a partial mesh & trade a little latency for memory.
fast-rtc-swarm is different.
- It's built on fast-rtc-peer, which is built on the new WebRTC v1.0 spec (transceivers instead of stage 2 tracks or stage 1 streams)
- The signaling server doesn't have to be server sent events. It can be anything. (see reference implementation)
- It doesn't bother the signaling server with a heartbeat. We can derive that info from the swarm by listening to
disconnected
on each peer. If timeouts are an issue, then add a WebSocket#ping on your server. Don't make the client do more work than necessary! - It only connects to 1 signaling server. Multiple servers is a proxy problem. Again, don't make the client work hard!
- No unauthenticated-by-default signaling server CLI. I'm not gonna make it easier for you to write an insecure server :-)
- No multiplexing streams. If you need a new data channel, open another one natively, not with expensive (and LARGE) stream packages.
- It uses the fast-rtc protocol for the fastest connection possible
The fast-rtc protocol completes a WebRTC handshake in only 2 round trips.
Other implementations take 3 (or even 4!)
It does this by keeping a peerBuffer
of offers and candidates.
Think of it like "pay-it-forward".
You buy the person behind you a coffee, so they get a free coffee when they get to register & buy the person behind them a coffee.
Here's how it works:
Alice is the first peer to join the swarm:
- She gives the server an OFFER that can be used by the next person to join
- As CANDIDATES and additional OFFERS trickle in, she forwards them to the server
- The server groups CANDIDATES and OFFERS in a single CHUNK, awaiting an ACCEPT
- When someone ACCEPTS her CHUNK, the server REQUESTS another from her
- As CANDIDATES and OFFERS continue to trickle in, they are forwarded to client that ACCEPTED the original chunk
When Bob joins the swarm:
- He follows the same procedure as Alice
- He takes one CHUNK from each client on the server
- If Alice does not have a CHUNK readily available, Bob puts his name on her waiting list
- On the client, Bob creates an ANSWER to each OFFER and forwards it to the signaling server
- The signaling server forwards Bob's ANSWER to Alice
- Alice uses Bob's ANSWER to initiate the connection
That's it! See reference implementation and the example below to see how to add it to your own server.
// client
import FastRTCSwarm from '@mattkrick/fast-rtc-swarm'
const socket = new WebSocket('ws://localhost:3000');
socket.addEventListener('open', () => {
const swarm = new FastRTCSwarm()
// send the signal to the signaling server
swarm.on('signal', (signal) => {
socket.send(JSON.stringify(signal))
})
// when the signal come back, dispatch it to the swarm
socket.addEventListener('message', (event) => {
const payload = JSON.parse(event.data)
swarm.dispatch(payload)
})
// when the connection is open, say hi to your new peer
swarm.on('dataOpen', (peer) => {
console.log('data channel open!')
peer.send('hi')
})
// when your peer says hi, log it
swarm.on('data', (data, peer) => {
console.log('data received', data, peer)
})
})
Config: A superset of fast-rtc-peer's config. To add a TURN server to the default list of ICE candidates, see fast-rtc-peer.
roomId
: a string to define the room all the peers are joiningpeerBuffer
: the number of additional offers to keep on the server (default 0)
Methods on FastRTCSwarm
addStreams(streamDict)
: similar to fast-rtc-peer'saddStreams
, but updates your tracks for future peersbroadcast(message)
: send a string or buffer to all connected peersclose()
: destroy all peer connectionsdispatch(signal)
: receive an incoming signal from the signal servermuteTrack(trackName)
: mute's the track for all peers & future peers
swarm.on('open', (peer) => {})
: fired when a peer connectsswarm.on('close', (peer) => {})
: fired when a peer disconnectsswarm.on('data', (data, peer) => {})
: fired when a peer sends dataswarm.on('signal', (signal, peer) => {})
: fired when a peer creates an offer, ICE candidate, or answer.swarm.on('stream', (stream, peer) => {})
: fired when a peer creates or updates an audio/video track.swarm.on('error', (error, peer) => {})
: fired when a peer has a signaling error.swarm.on('connection', (stream, peer) => {})
: fired when a peer's ICE connection state changes
MIT