WebRTC & Live Collaboration
Peer-to-peer video, real-time data channels, and building shared-canvas experiences with PeerJS and WebRTC.
What is WebRTC?
WebRTC (Web Real-Time Communication) is a browser API for peer-to-peer media and data. Unlike HTTP (server intermediary), WebRTC sends data directly between browsers — low latency, no server bandwidth costs.
Two use cases for creative coding:
- Video/audio streams — receiving another person’s camera as a texture
- Data channels — sending cursor positions, events, or canvas state in real time
PeerJS — simpler WebRTC
PeerJS wraps the complex WebRTC API:
<script src="https://unpkg.com/peerjs@1.5.4/dist/peerjs.min.js"></script>
PeerJS still needs a signalling server (to exchange connection info before the direct connection is established). PeerJS provides a free cloud server for development; deploy your own for production.
Basic data channel — shared cursor
Sender (broadcaster):
let peer, conn;
let myId = 'broadcaster-' + floor(random(10000));
function setup() {
createCanvas(700, 500);
peer = new Peer(myId);
peer.on('connection', c => {
conn = c;
print('Connected:', c.peer);
});
print('My ID:', myId);
}
function draw() {
background(20);
fill(255, 100, 50);
noStroke();
circle(mouseX, mouseY, 20);
if (conn && conn.open) {
conn.send({ x: mouseX, y: mouseY, t: frameCount });
}
}
Receiver (viewer):
let peer, conn;
let remote = { x: 0, y: 0 };
function setup() {
createCanvas(700, 500);
peer = new Peer();
peer.on('open', id => {
print('My ID:', id);
conn = peer.connect('broadcaster-XXXX'); // sender's ID
conn.on('data', data => { remote = data; });
});
}
function draw() {
background(20);
fill(100, 200, 255);
noStroke();
circle(remote.x, remote.y, 20);
}
Shared drawing canvas
Two or more users drawing on the same canvas:
let peer, connections = [];
let strokes = [];
function setup() {
createCanvas(800, 600);
background(250);
peer = new Peer();
peer.on('open', id => {
print('Your room code:', id);
document.title = id;
});
peer.on('connection', conn => {
setupConn(conn);
});
}
function setupConn(conn) {
connections.push(conn);
conn.on('data', data => {
if (data.type === 'stroke') {
applyStroke(data);
}
});
}
function joinRoom(hostId) {
let conn = peer.connect(hostId);
setupConn(conn);
}
function mouseDragged() {
let stroke = {
type: 'stroke',
x1: pmouseX, y1: pmouseY,
x2: mouseX, y2: mouseY,
colour: myColour,
weight: brushSize
};
applyStroke(stroke);
broadcast(stroke);
}
function applyStroke(s) {
stroke(s.colour);
strokeWeight(s.weight);
line(s.x1, s.y1, s.x2, s.y2);
}
function broadcast(data) {
for (let c of connections) {
if (c.open) c.send(data);
}
}
Video as a canvas texture
Receive a peer’s webcam and render it as a p5.js texture:
let peer, remoteVideo;
function setup() {
createCanvas(640, 480);
peer = new Peer();
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then(stream => {
peer.on('call', call => {
call.answer(stream);
call.on('stream', remoteStream => {
remoteVideo = createElement('video');
remoteVideo.elt.srcObject = remoteStream;
remoteVideo.elt.play();
remoteVideo.hide();
});
});
});
}
function draw() {
background(20);
if (remoteVideo && remoteVideo.elt.readyState >= 2) {
image(remoteVideo, 0, 0, width, height);
}
}
Multi-user state synchronisation
For installations with many participants, broadcast game state rather than individual events:
// State reconciliation pattern
let localState = {};
let remoteState = {};
let displayedState = {};
function update() {
// Blend local and remote states
for (let key in remoteState) {
if (!displayedState[key]) displayedState[key] = remoteState[key];
displayedState[key] = lerp(displayedState[key], remoteState[key], 0.2);
}
}
// Send state every N frames
if (frameCount % 3 === 0 && conn && conn.open) {
conn.send({ type: 'state', data: localState });
}
// Receive
conn.on('data', msg => {
if (msg.type === 'state') remoteState = msg.data;
});
Production considerations
- Signalling server: PeerJS Cloud is free but limited. Deploy peerjs-server on a VPS for reliability
- NAT traversal: WebRTC uses STUN/TURN servers. PeerJS includes free STUN; TURN servers (for relaying behind restrictive firewalls) require your own or a service like Twilio
- Data channel limits: each PeerJS data message is JSON-serialised; for high-frequency data, keep payloads small
- Reconnection: always handle
conn.on('close')andconn.on('error')to reconnect gracefully
Key takeaways
- WebRTC enables peer-to-peer data and video with low latency
- PeerJS abstracts the WebRTC handshake — peers connect via an ID
- Data channels send arbitrary JSON between peers; use them for cursor sync, drawing events, or game state
- Remote video streams can be drawn to the canvas with
image(remoteVideo, 0, 0) - For multi-user sync, send compressed state snapshots rather than every event
- Deploy your own PeerJS signalling server for production reliability