Let's dive deep into Phase 1: Foundations of WebRTC and make sure you understand every corner of it.
- Pre-WebRTC Era:
Before WebRTC, real-time communication relied on Flash, Java applets, and third-party plugins. These solutions had security issues, high latency, and poor browser support. - Googleβs Acquisition & Open Source Contribution:
- In 2010, Google acquired Global IP Solutions (GIPS), a company specializing in real-time voice and video processing.
- In 2011, Google open-sourced WebRTC and submitted it to the W3C (World Wide Web Consortium) and IETF (Internet Engineering Task Force) to standardize the technology.
- Standardization & Widespread Adoption:
- WebRTC became a W3C Recommendation in 2021, making it an official web standard.
- Now, WebRTC is widely used in applications like Google Meet, Discord, WhatsApp Web, Microsoft Teams, and more.
- Peer-to-Peer (P2P) Communication: Direct connection between users, reducing latency.
- Real-Time Media Streaming: Supports live video/audio without extra plugins.
- Data Transfer: Exchange arbitrary data (files, messages) via RTCDataChannel.
- End-to-End Encryption (E2EE): Ensures secure communication by using DTLS and SRTP.
- Cross-Platform Support: Works on browsers, mobile apps, and servers.
Use Case | How WebRTC Helps |
---|---|
Video Calls | Enables peer-to-peer video/audio chat applications. |
Live Streaming | Can be used to broadcast events with low latency. |
Screen Sharing | Share screens without third-party software using getDisplayMedia() . |
Online Gaming | WebRTC's RTCDataChannel allows real-time game state sync. |
IoT & Surveillance | Enables live camera streaming from IoT devices. |
Feature | WebRTC | WebSockets | RTP | SIP |
---|---|---|---|---|
Real-Time Audio/Video | β Yes | β No | β Yes | β No |
Peer-to-Peer | β Yes | β No (Client-Server) | β Yes | β No |
Text & Binary Data | β Yes | β Yes | β No | β No |
Requires a Server | β No (after signaling) | β Yes | β Yes | β Yes |
Encryption | β Yes (DTLS/SRTP) | β Yes (TLS/SSL) | β No | β Yes |
- Sockets/WebSockets β Good for bidirectional text communication but cannot handle media streams.
- RTP (Real-Time Transport Protocol) β Used in VoIP and video conferencing but requires SIP for session management.
- SIP (Session Initiation Protocol) β Handles call setup and teardown, often used with RTP but lacks native browser support.
- WebRTC combines the best of these technologies, offering peer-to-peer, real-time communication with encryption.
WebRTC enables direct communication between users. However, establishing this direct connection is not simple due to NAT (Network Address Translation) and Firewalls. Letβs break it down step by step.
The three major steps to establishing a WebRTC connection:
- Signaling β Exchanging connection details using a signaling server.
- ICE Candidate Exchange β Finding the best route for peer-to-peer connection.
- Direct Peer-to-Peer Communication β Once established, media/data flows directly.
π‘ WebRTC does not handle signaling! You must implement it using WebSockets, Firebase, MQTT, or any other communication protocol.
WebRTC is supported natively in browsers like Chrome, Firefox, Safari, Edge. The three core WebRTC APIs:
API | Purpose |
---|---|
getUserMedia() |
Captures audio/video from the userβs camera/microphone. |
RTCPeerConnection |
Establishes a peer-to-peer connection and handles ICE candidates. |
RTCDataChannel |
Enables peer-to-peer data transfer (text, files, game state sync). |
- SDP is not a protocol but a format used to exchange connection information between peers.
- It contains details like IP addresses, ports, media codecs, encryption methods.
π‘ Analogy:
Imagine SDP as a handshake protocol where two users say:
- Alice: "I support H.264 video and Opus audio, can you understand that?"
- Bob: "Yes, I also support H.264 and Opus, let's communicate!"
v=0
o=- 123456789 2 IN IP4 192.168.1.5
s=WebRTC Call
c=IN IP4 192.168.1.5
t=0 0
m=audio 5004 RTP/AVP 96
a=rtpmap:96 opus/48000
m=video 5006 RTP/AVP 97
a=rtpmap:97 H264/90000
m=audio
β Specifies an audio stream.m=video
β Specifies a video stream.a=rtpmap
β Defines codecs like Opus (audio) and H.264 (video).
ICE is a framework used to find the best way for two peers to connect.
- Why do we need ICE?
- Devices can be behind NAT or firewalls, making direct connection difficult.
- ICE gathers different network candidates and tests them.
- Used to discover a device's public IP.
- Helps in most cases when NAT is not restrictive.
- Example Public STUN Server:
stun:stun.l.google.com:19302
- When direct connection fails, TURN relays traffic through a server.
- This is slower but necessary in strict NAT/firewall setups.
- Running a TURN Server (Coturn)
coturn --realm=mywebrtcapp.com --user=username:password
π‘ Analogy:
- STUN is like asking a friend for their home address and then going directly.
- TURN is like sending messages through a middleman because you can't reach your friend directly.
- WebRTC is not just video calls; itβs a complete framework for real-time communication.
- Understanding SDP, ICE, STUN, and TURN is essential for mastering WebRTC.
- Next, weβll move into Phase 2: Core WebRTC APIs to start coding WebRTC apps in React & Node.js.
π Do you want to start with code examples for getUserMedia()
and RTCPeerConnection
, or do you have any questions so far? π
Think of WebRTC like a phone call between browsers without needing a middleman (like Zoom or Skype). But since browsers donβt know each other, they need a way to find, connect, and talk securely. Thatβs what WebRTC does.
π A messenger that helps two people exchange info to start a call.
π‘ WebRTC DOESNβT handle signaling itself! You need WebSockets, Firebase, or another service to send messages.
π Example of a Signaling Message:
{
"type": "offer",
"sdp": "v=0..."
}
(Browser A sends this to Browser B to start a call)
π A pipe between two browsers for sending audio, video, and data.
π‘ This is the most important WebRTC API!
π How to create a connection:
const peerConnection = new RTCPeerConnection();
π A way for two people to find the best way to connect (like checking multiple roads to see which one works).
π‘ Uses STUN/TURN servers to get around firewalls & NATs.
π How ICE candidates look:
{
"candidate": "candidate:842163049 1 udp 1677729535 192.168.1.1 4000 typ host"
}
πΉ STUN (Session Traversal Utilities for NAT)
π A helper server that tells you your public IP so others can connect to you.
π‘ Fast & Free, but fails if strict firewalls block traffic.
πΉ TURN (Traversal Using Relays around NAT)
π A relay server that sends your media through itself if direct connection fails.
π‘ Slower & Expensive, but always works.
π Googleβs Free STUN Server:
const config = { iceServers: [{ urls: "stun:stun.l.google.com:19302" }] };
π A set of rules that tells two browsers how to talk (like picking a language before chatting).
π‘ Contains IP, Port, Audio/Video Codecs, and other connection details.
π Example of an SDP Offer:
{
"type": "offer",
"sdp": "v=0\no=- 4611732547713599112 2 IN IP4 127.0.0.1..."
}
πΉ MediaStream β The whole video/audio stream.
πΉ Track β A single audio or video inside the stream.
π Example:
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then(stream => {
peerConnection.addTrack(stream.getTracks()[0], stream);
});
π A fast way to send text, files, and game data directly between peers.
π‘ Think of it like WebSockets but faster!
π Example:
const dataChannel = peerConnection.createDataChannel("chat");
dataChannel.send("Hello, WebRTC!");
1οΈβ£ User requests video/audio β (getUserMedia()
)
2οΈβ£ Peers exchange signaling messages (via WebSockets, Firebase, etc.)
3οΈβ£ Offer & Answer are exchanged (using SDP)
4οΈβ£ ICE Candidates are exchanged (STUN/TURN helps find the best route)
5οΈβ£ RTCPeerConnection is established (Media/Data start flowing)
6οΈβ£ Connection stays active (Handles network changes & packet loss)
7οΈβ£ Call Ends (Connection is closed)
π₯ This is the foundation of WebRTC. Master these and you're already ahead of most developers! π
Let's dive into Phase 2: Core WebRTC APIs and Internals in detail. We'll cover each API with its internal working, usage, and practical examples to ensure you master WebRTC at a deep level.
The getUserMedia()
API allows a web app to access the user's microphone and camera. It returns a MediaStream
, which contains audio/video tracks.
π‘ Under the Hood:
- The browser asks for user permission before accessing media.
- Once granted, it creates tracks (video/audio) and sends them as a stream.
- The stream can be played in a
<video>
element, processed, or sent over WebRTC.
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then((stream) => {
document.getElementById("videoElement").srcObject = stream;
})
.catch((error) => console.error("Error accessing media:", error));
const constraints = {
video: {
width: { min: 640, ideal: 1280, max: 1920 },
height: { min: 360, ideal: 720, max: 1080 },
frameRate: { max: 30 },
facingMode: "user", // 'environment' for back camera
},
audio: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
}
};
navigator.mediaDevices.getUserMedia(constraints)
.then((stream) => { /* Use stream */ })
.catch((error) => console.error("Error:", error));
Constraint | Description |
---|---|
width , height |
Defines resolution (min, max, ideal). |
frameRate |
Max FPS (useful for smoother streaming). |
facingMode |
"user" (front camera), "environment" (back). |
echoCancellation , noiseSuppression |
Improves audio quality. |
1οΈβ£ Applying CSS Filters to Video
<video id="videoElement" autoplay style="filter: grayscale(100%)"></video>
2οΈβ£ Applying a WebGL Filter (Green Screen)
const video = document.getElementById("videoElement");
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
navigator.mediaDevices.getUserMedia({ video: true }).then((stream) => {
video.srcObject = stream;
requestAnimationFrame(updateCanvas);
});
function updateCanvas() {
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
let frame = ctx.getImageData(0, 0, canvas.width, canvas.height);
for (let i = 0; i < frame.data.length; i += 4) {
let green = frame.data[i + 1]; // Green channel
if (green > 100) { frame.data[i + 3] = 0; } // Make green pixels transparent
}
ctx.putImageData(frame, 0, 0);
requestAnimationFrame(updateCanvas);
}
πΉ This removes the green screen (chroma keying).
The RTCPeerConnection
API is used to establish a secure peer-to-peer connection.
1οΈβ£ Caller creates an RTCPeerConnection
instance.
2οΈβ£ Caller creates an SDP offer and sends it to the callee.
3οΈβ£ Callee receives the offer, creates an SDP answer, and sends it back.
4οΈβ£ Both exchange ICE candidates to establish a connection.
const peerConnection = new RTCPeerConnection();
// Add local stream to connection
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then((stream) => {
stream.getTracks().forEach(track => peerConnection.addTrack(track, stream));
});
// Create and send SDP Offer
peerConnection.createOffer().then((offer) => {
return peerConnection.setLocalDescription(offer);
}).then(() => {
sendSignalingData({ type: "offer", sdp: peerConnection.localDescription });
});
const peerConnection = new RTCPeerConnection();
receiveSignalingData((data) => {
if (data.type === "offer") {
peerConnection.setRemoteDescription(new RTCSessionDescription(data.sdp));
peerConnection.createAnswer().then((answer) => {
return peerConnection.setLocalDescription(answer);
}).then(() => {
sendSignalingData({ type: "answer", sdp: peerConnection.localDescription });
});
}
});
WebRTC needs ICE candidates to establish a stable connection.
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
sendSignalingData({ type: "candidate", candidate: event.candidate });
}
};
On the receiver side:
receiveSignalingData((data) => {
if (data.type === "candidate") {
peerConnection.addIceCandidate(new RTCIceCandidate(data.candidate));
}
});
- WebRTC encrypts media streams using DTLS-SRTP.
- SRTP secures audio/video streams, preventing eavesdropping.
- Encryption keys are exchanged via DTLS (Datagram Transport Layer Security).
WebRTC supports peer-to-peer data transfer (text, files, game data).
const dataChannel = peerConnection.createDataChannel("chat");
// Send messages
dataChannel.send("Hello, WebRTC!");
peerConnection.ondatachannel = (event) => {
const dataChannel = event.channel;
dataChannel.onmessage = (msgEvent) => {
console.log("Received:", msgEvent.data);
};
};
Mode | Reliable? | Ordered? | Use Case |
---|---|---|---|
reliable: true |
β Yes | β Yes | File transfer, messaging |
reliable: false |
β No | β No | Live gaming, real-time telemetry |
ordered: false |
β Yes | β No | VoIP, video frames |
const dataChannel = peerConnection.createDataChannel("game", {
ordered: false, // Packet loss is acceptable
maxRetransmits: 3 // Retransmit up to 3 times
});
1οΈβ£ Low-latency multiplayer games (e.g., real-time state updates).
2οΈβ£ Decentralized chat apps (without a central server).
3οΈβ£ Live file sharing (P2P).
- Youβve now mastered WebRTCβs core APIs π.
- Next, weβll move into Phase 3: Advanced WebRTC Features (Screen Sharing, Simulcast, Mesh vs. SFU, Recording).
Now, letβs move into Phase 3: WebRTC Signaling & Communication, where we cover how peers discover and connect to each other.
WebRTC provides peer-to-peer communication, but signaling (coordination) happens outside WebRTC.
Since WebRTC does not handle signaling, we need a mechanism to:
β
Exchange Session Descriptions (SDP) β Helps peers agree on audio/video formats.
β
Send ICE Candidates β Finds the best network path for communication.
β
Detect Disconnections β Handles reconnections when needed.
π‘ Think of Signaling as the "handshake" before direct communication begins.
πΉ WebSockets provide real-time, bidirectional communication over TCP.
const socket = new WebSocket("wss://your-server.com");
socket.onopen = () => console.log("Connected to signaling server");
socket.onmessage = (message) => {
const data = JSON.parse(message.data);
if (data.type === "offer") {
handleOffer(data.offer);
} else if (data.type === "answer") {
handleAnswer(data.answer);
} else if (data.type === "candidate") {
handleNewICECandidate(data.candidate);
}
};
function sendMessage(message) {
socket.send(JSON.stringify(message));
}
πΈ REST API β Works but not real-time. Requires polling.
πΈ Firebase Realtime Database β Good for small projects.
πΈ MQTT (Message Queue Telemetry Transport) β Used in IoT but not optimal for WebRTC.
Example of using Firebase as a signaling server:
import { ref, set, onValue } from "firebase/database";
const signalingRef = ref(database, "signaling");
function sendSignalingMessage(type, data) {
set(signalingRef, { type, data });
}
onValue(signalingRef, (snapshot) => {
const message = snapshot.val();
if (message?.type === "offer") handleOffer(message.data);
});
π WebSockets are the most recommended option for production apps.
Let's build a real-time signaling server using WebSockets and Node.js.
npm init -y
npm install ws express
const WebSocket = require("ws");
const server = new WebSocket.Server({ port: 8080 });
let clients = [];
server.on("connection", (socket) => {
clients.push(socket);
socket.on("message", (message) => {
clients.forEach((client) => {
if (client !== socket && client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
});
socket.on("close", () => {
clients = clients.filter((client) => client !== socket);
});
});
console.log("WebSocket signaling server running on ws://localhost:8080");
πΉ This broadcasts signaling messages to all connected clients.
π The WebRTC flow works like this:
1οΈβ£ Caller sends an SDP offer via WebSockets.
2οΈβ£ Callee responds with an SDP answer.
3οΈβ£ Both peers exchange ICE candidates.
const peer = new RTCPeerConnection();
peer.onicecandidate = (event) => {
if (event.candidate) {
sendMessage({ type: "candidate", candidate: event.candidate });
}
};
// Create an offer
peer.createOffer().then((offer) => {
return peer.setLocalDescription(offer);
}).then(() => {
sendMessage({ type: "offer", offer: peer.localDescription });
});
socket.onmessage = (message) => {
const data = JSON.parse(message.data);
if (data.type === "offer") {
peer.setRemoteDescription(new RTCSessionDescription(data.offer));
peer.createAnswer().then((answer) => {
return peer.setLocalDescription(answer);
}).then(() => {
sendMessage({ type: "answer", answer: peer.localDescription });
});
} else if (data.type === "candidate") {
peer.addIceCandidate(new RTCIceCandidate(data.candidate));
}
};
π Once ICE candidates are exchanged, WebRTC will connect the peers directly! π
π‘ STUN and TURN help bypass network restrictions (NAT, Firewalls).
Server Type | Function |
---|---|
STUN (Session Traversal Utilities for NAT) | Finds public IP address, helps NAT traversal. |
TURN (Traversal Using Relays around NAT) | Relays traffic when direct connection fails. |
πΉ STUN is lightweight but doesnβt work for restrictive firewalls (Carrier-Grade NAT).
πΉ TURN is slower but guarantees connectivity by relaying media.
WebRTC provides Googleβs free STUN servers:
const peer = new RTCPeerConnection({
iceServers: [{ urls: "stun:stun.l.google.com:19302" }]
});
πΉ For production, set up a TURN server (e.g., Coturn).
sudo apt update && sudo apt install coturn
lt-cred-mech
use-auth-secret
static-auth-secret=your_secret_key
realm=yourdomain.com
listening-port=3478
sudo systemctl restart coturn
sudo systemctl enable coturn
const peer = new RTCPeerConnection({
iceServers: [
{ urls: "stun:stun.l.google.com:19302" }, // Public STUN
{ urls: "turn:your-turn-server.com", username: "user", credential: "pass" } // Private TURN
]
});
π TURN servers ensure connectivity but require hosting and authentication.
β
Built a WebSocket-based Signaling Server.
β
Implemented SDP and ICE Candidate Exchange.
β
Understood STUN/TURN for NAT Traversal.
β
Set up a Private TURN Server for Production.
πΉ Screen Sharing
πΉ Recording WebRTC Streams
πΉ SFU vs. Mesh Networks
Now, let's dive into Phase 4: Advanced WebRTC Features π
πΉ WebRTC allows real-time video processing using Canvas & WebGL.
const video = document.querySelector("video");
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
function applyBlurEffect() {
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
ctx.filter = "blur(5px)";
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
requestAnimationFrame(applyBlurEffect);
}
video.addEventListener("play", applyBlurEffect);
π‘ Use WebGL shaders for more advanced effects like background removal.
πΉ WebRTC provides getDisplayMedia() to share the screen.
async function startScreenShare() {
try {
const stream = await navigator.mediaDevices.getDisplayMedia({ video: true });
document.querySelector("video").srcObject = stream;
} catch (error) {
console.error("Screen share error:", error);
}
}
π For security reasons, browsers require user permission for screen sharing.
πΉ WebRTC allows recording both local and remote streams.
let recorder;
let recordedChunks = [];
function startRecording(stream) {
recorder = new MediaRecorder(stream);
recorder.ondataavailable = (event) => recordedChunks.push(event.data);
recorder.onstop = () => {
const blob = new Blob(recordedChunks, { type: "video/webm" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "recorded-video.webm";
a.click();
};
recorder.start();
}
π Use WebRTC recording for saving meetings, lectures, or gameplay.
πΉ Three architectures for multi-party WebRTC calls:
Architecture | How it Works | Pros | Cons |
---|---|---|---|
Mesh | Each peer connects directly to every other peer. | Simple, no servers. | High bandwidth usage. |
SFU (Selective Forwarding Unit) | A central server forwards streams to all participants. | Efficient bandwidth, low latency. | Requires a media server. |
MCU (Multipoint Control Unit) | Server mixes video streams and sends a single combined stream to users. | Low CPU usage for clients. | High server costs. |
πΉ Mediasoup is a popular SFU for WebRTC-based applications.
npm install mediasoup
const mediasoup = require("mediasoup");
async function createSFU() {
const worker = await mediasoup.createWorker();
const router = await worker.createRouter({
mediaCodecs: [{ kind: "video", mimeType: "video/VP8" }],
});
console.log("SFU is running...");
}
createSFU();
π SFU servers relay media, reducing peer-to-peer bandwidth consumption.
πΉ WebRTC dynamically adjusts quality based on network conditions.
const sender = peerConnection.getSenders().find((s) => s.track.kind === "video");
const params = sender.getParameters();
params.encodings[0].maxBitrate = 500000; // 500 kbps
sender.setParameters(params);
π This prevents WebRTC from using too much bandwidth.
πΉ WebRTC uses Forward Error Correction (FEC) and Retransmissions (RTX) to recover lost packets.
const peer = new RTCPeerConnection({
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
encodedInsertableStreams: true,
});
π This improves video quality in poor network conditions.
β
Added real-time filters to WebRTC streams.
β
Implemented screen sharing & media recording.
β
Explored Mesh, SFU, and MCU for multi-peer calls.
β
Optimized WebRTC performance with bandwidth control & error correction.
πΉ Deploying a WebRTC App
πΉ Security Best Practices
πΉ Integrating WebRTC with React
Now, let's tackle Phase 5: Security, Deployment & Scaling ππ
π WebRTC encrypts all media and data by default using:
β
DTLS (Datagram Transport Layer Security) β Encrypts signaling
β
SRTP (Secure Real-time Transport Protocol) β Encrypts media streams
const peer = new RTCPeerConnection({
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
certificates: [RTCCertificate.generateCertificate({ name: "ECDSA", namedCurve: "P-256" })]
});
π This ensures the connection uses a secure DTLS certificate.
Even though WebRTC encrypts media, signaling is not encrypted by default! π¨
Solution:
β
Use WSS (WebSocket Secure) instead of WS
β
Encrypt SDP messages
β
Prevent unauthorized access with authentication
const wss = new WebSocket("wss://your-secure-server.com");
wss.onmessage = (message) => {
const data = JSON.parse(message.data);
if (data.sdp) {
peerConnection.setRemoteDescription(new RTCSessionDescription(data.sdp));
}
};
π Never expose raw SDP or ICE candidates to untrusted users.
By default, WebRTC can expose your private IP address due to ICE candidate discovery.
Solution:
β
Disable non-essential ICE candidates
β
Use a TURN server to mask the real IP
const peer = new RTCPeerConnection({
iceServers: [{ urls: "turn:your-turn-server.com", username: "user", credential: "pass" }],
iceTransportPolicy: "relay" // Forces WebRTC to use TURN
});
π This prevents WebRTC from revealing local network IP addresses.
To deploy a WebRTC app, you need:
β
Frontend Hosting (Vercel, Netlify, S3, Firebase Hosting)
β
Signaling Server (Node.js with WebSockets)
β
TURN/STUN Servers (Coturn, Twilio, Google STUN)
npm install -g pm2
pm2 start server.js --name webrtc-signaling
pm2 save
pm2 startup
π PM2 keeps your signaling server running even after crashes.
Challenges in Scaling WebRTC:
β Peer-to-peer connections donβt scale well
β Too many clients β High bandwidth usage
β TURN servers get overloaded
Solutions:
β
Use an SFU (Selective Forwarding Unit) like Mediasoup
β
Deploy TURN/STUN on AWS or DigitalOcean
β
Use WebRTC Load Balancing with Cloudflare or Nginx
coturn -c /etc/turnserver.conf --min-port=49152 --max-port=65535
π Use multiple TURN servers to distribute the load.
β
Open chrome://webrtc-internals/
β
Inspect ICE Candidates, SDP, and DTLS Handshakes
β
Identify packet loss, latency, and jitter
chrome://webrtc-internals/
π If ICE fails, check if your TURN server is misconfigured.
Issue | Cause | Fix |
---|---|---|
No audio/video | Media permissions blocked | Ensure getUserMedia() permissions are granted |
ICE Connection Failed | Firewall blocking UDP | Use a TURN server |
High latency in calls | Poor network conditions | Reduce bitrate & enable error correction |
Video lagging/freezing | CPU overload | Optimize video encoding & resolution |
β
WebRTC security: Encryption, Secure Signaling, IP Leak Prevention
β
Deployed WebRTC with PM2, Coturn, and Cloudflare
β
Used Chromeβs WebRTC Internals to debug errors
β
Learned fixes for common WebRTC issues
Let's build a 1:1 Video Calling App using React (Frontend) + Node.js (Backend) + WebRTC + WebSockets from scratch. π
We'll follow these steps:
β
Set up a React frontend for video streaming πΉ
β
Build a Node.js WebSocket signaling server π
β
Connect two users with WebRTC peer-to-peer communication π
npx create-react-app webrtc-app
cd webrtc-app
npm install socket.io-client
Create a WebRTCComponent.js file:
import React, { useEffect, useRef, useState } from "react";
import io from "socket.io-client";
const socket = io("http://localhost:5000"); // Connect to WebSocket Server
const WebRTCComponent = () => {
const localVideoRef = useRef(null);
const remoteVideoRef = useRef(null);
const [peerConnection, setPeerConnection] = useState(null);
useEffect(() => {
// Get User Media
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then((stream) => {
localVideoRef.current.srcObject = stream;
const pc = new RTCPeerConnection({
iceServers: [{ urls: "stun:stun.l.google.com:19302" }]
});
// Add local stream to peer connection
stream.getTracks().forEach((track) => pc.addTrack(track, stream));
setPeerConnection(pc);
// Handle ICE Candidate Exchange
pc.onicecandidate = (event) => {
if (event.candidate) {
socket.emit("ice-candidate", event.candidate);
}
};
// Receive Remote Stream
pc.ontrack = (event) => {
remoteVideoRef.current.srcObject = event.streams[0];
};
// Listen for SDP Offers
socket.on("offer", async (offer) => {
await pc.setRemoteDescription(new RTCSessionDescription(offer));
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
socket.emit("answer", answer);
});
// Listen for SDP Answers
socket.on("answer", async (answer) => {
await pc.setRemoteDescription(new RTCSessionDescription(answer));
});
// Listen for ICE Candidates
socket.on("ice-candidate", async (candidate) => {
try {
await pc.addIceCandidate(new RTCIceCandidate(candidate));
} catch (error) {
console.error("Error adding ICE candidate:", error);
}
});
})
.catch((error) => console.error("Error accessing media devices:", error));
}, []);
// Function to Start a Call
const startCall = async () => {
if (!peerConnection) return;
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
socket.emit("offer", offer);
};
return (
<div>
<h1>WebRTC 1:1 Video Call</h1>
<video ref={localVideoRef} autoPlay playsInline muted />
<video ref={remoteVideoRef} autoPlay playsInline />
<button onClick={startCall}>Start Call</button>
</div>
);
};
export default WebRTCComponent;
mkdir server
cd server
npm init -y
npm install express socket.io cors
Create server.js inside the server
folder:
const express = require("express");
const http = require("http");
const { Server } = require("socket.io");
const cors = require("cors");
const app = express();
const server = http.createServer(app);
const io = new Server(server, {
cors: { origin: "http://localhost:3000", methods: ["GET", "POST"] }
});
io.on("connection", (socket) => {
console.log("New client connected:", socket.id);
socket.on("offer", (offer) => {
socket.broadcast.emit("offer", offer);
});
socket.on("answer", (answer) => {
socket.broadcast.emit("answer", answer);
});
socket.on("ice-candidate", (candidate) => {
socket.broadcast.emit("ice-candidate", candidate);
});
socket.on("disconnect", () => {
console.log("Client disconnected");
});
});
server.listen(5000, () => console.log("Server running on port 5000"));
cd server
node server.js
cd webrtc-app
npm start
- Open http://localhost:3000 in two browser tabs.
- Click the "Start Call" button in one tab.
- The other tab should receive the call and connect.
β
Basic 1:1 Video Calling is working! π
β‘οΈ Want to add Screen Sharing, Group Calls, or Recording? π₯
We'll extend our 1:1 WebRTC setup to support multiple users using Mesh Topology (each peer connects to every other peer).
- Update the Frontend β Allow multiple video streams
- Modify WebRTC Logic β Support multiple peer connections
- Update the Signaling Server β Handle multiple users
Install dependencies if not already installed
npm install socket.io-client
This component manages multiple peer connections and displays multiple video streams.
import React, { useEffect, useRef, useState } from "react";
import io from "socket.io-client";
const socket = io("http://localhost:5000"); // Connect to WebSocket Server
const ICE_SERVERS = [{ urls: "stun:stun.l.google.com:19302" }];
const GroupCall = () => {
const [peers, setPeers] = useState({});
const [stream, setStream] = useState(null);
const videoRefs = useRef({}); // Store video elements dynamically
useEffect(() => {
// Get User Media
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then((mediaStream) => {
setStream(mediaStream);
socket.emit("join-room"); // Notify server that we joined
})
.catch((error) => console.error("Media access error:", error));
// Handle New User Connection
socket.on("user-joined", (userId) => {
const peerConnection = createPeerConnection(userId);
setPeers((prev) => ({ ...prev, [userId]: peerConnection }));
});
// Handle Incoming Offers
socket.on("offer", async ({ senderId, offer }) => {
const peerConnection = createPeerConnection(senderId);
await peerConnection.setRemoteDescription(new RTCSessionDescription(offer));
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
socket.emit("answer", { receiverId: senderId, answer });
setPeers((prev) => ({ ...prev, [senderId]: peerConnection }));
});
// Handle Incoming Answers
socket.on("answer", ({ senderId, answer }) => {
peers[senderId]?.setRemoteDescription(new RTCSessionDescription(answer));
});
// Handle Incoming ICE Candidates
socket.on("ice-candidate", ({ senderId, candidate }) => {
peers[senderId]?.addIceCandidate(new RTCIceCandidate(candidate));
});
// Handle User Disconnection
socket.on("user-left", (userId) => {
peers[userId]?.close();
setPeers((prev) => {
const updatedPeers = { ...prev };
delete updatedPeers[userId];
return updatedPeers;
});
});
return () => {
socket.off("user-joined");
socket.off("offer");
socket.off("answer");
socket.off("ice-candidate");
socket.off("user-left");
};
}, []);
// Function to Create a New Peer Connection
const createPeerConnection = (userId) => {
const peerConnection = new RTCPeerConnection({ iceServers: ICE_SERVERS });
// Add local stream to the peer connection
if (stream) {
stream.getTracks().forEach((track) => peerConnection.addTrack(track, stream));
}
// Handle ICE Candidates
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
socket.emit("ice-candidate", { receiverId: userId, candidate: event.candidate });
}
};
// Handle Incoming Tracks
peerConnection.ontrack = (event) => {
videoRefs.current[userId].srcObject = event.streams[0];
};
return peerConnection;
};
// Function to Start a Call (Only Used by First User)
const startCall = async () => {
for (const userId in peers) {
const offer = await peers[userId].createOffer();
await peers[userId].setLocalDescription(offer);
socket.emit("offer", { receiverId: userId, offer });
}
};
return (
<div>
<h1>Group Video Call</h1>
<video ref={(el) => (videoRefs.current["self"] = el)} autoPlay playsInline muted />
{Object.keys(peers).map((userId) => (
<video key={userId} ref={(el) => (videoRefs.current[userId] = el)} autoPlay playsInline />
))}
<button onClick={startCall}>Start Call</button>
</div>
);
};
export default GroupCall;
Update server.js to handle multiple users.
const express = require("express");
const http = require("http");
const { Server } = require("socket.io");
const cors = require("cors");
const app = express();
const server = http.createServer(app);
const io = new Server(server, {
cors: { origin: "http://localhost:3000", methods: ["GET", "POST"] }
});
let users = {}; // Store connected users
io.on("connection", (socket) => {
console.log(`User connected: ${socket.id}`);
socket.on("join-room", () => {
users[socket.id] = socket;
socket.broadcast.emit("user-joined", socket.id);
});
socket.on("offer", ({ receiverId, offer }) => {
if (users[receiverId]) {
users[receiverId].emit("offer", { senderId: socket.id, offer });
}
});
socket.on("answer", ({ receiverId, answer }) => {
if (users[receiverId]) {
users[receiverId].emit("answer", { senderId: socket.id, answer });
}
});
socket.on("ice-candidate", ({ receiverId, candidate }) => {
if (users[receiverId]) {
users[receiverId].emit("ice-candidate", { senderId: socket.id, candidate });
}
});
socket.on("disconnect", () => {
console.log(`User disconnected: ${socket.id}`);
delete users[socket.id];
socket.broadcast.emit("user-left", socket.id);
});
});
server.listen(5000, () => console.log("Server running on port 5000"));
Start the Signaling Server:
cd server
node server.js
Start the React Frontend:
cd webrtc-app
npm start
β
User joins the room β Notifies others
β
Each user creates a WebRTC connection with every other peer
β
ICE candidates and SDP are exchanged via WebSockets
β
Multiple video streams appear dynamically
- Optimize Performance: Switch from Mesh Topology to SFU (Selective Forwarding Unit)
- Enhance UI: Display user names, mute/unmute, UI improvements
- Add Screen Sharing & Recording
Live streaming with WebRTC involves one broadcaster (host) and multiple viewers (audience). Unlike peer-to-peer (P2P) calls, a more efficient approach is to use Selective Forwarding Units (SFU) instead of Mesh Topology, allowing a single broadcaster to stream to multiple viewers.
- Broadcaster captures media using
getUserMedia()
and sends it to an SFU/WebRTC server. - Server (SFU) forwards the stream to multiple viewers using WebRTC.
- Viewers receive the stream via
RTCPeerConnection
and display it.
π Why SFU and Not Mesh?
- Mesh: High bandwidth usage, not scalable.
- SFU: Efficient, single upstream from broadcaster, multiple downstreams to viewers.
npm install socket.io socket.io-client express cors
π server.js
(Signaling + Media Relay)
const express = require("express");
const http = require("http");
const { Server } = require("socket.io");
const cors = require("cors");
const app = express();
app.use(cors());
const server = http.createServer(app);
const io = new Server(server, {
cors: { origin: "http://localhost:3000", methods: ["GET", "POST"] }
});
let broadcaster = null;
let viewers = {};
io.on("connection", (socket) => {
console.log("User connected:", socket.id);
socket.on("broadcaster", () => {
broadcaster = socket.id;
socket.broadcast.emit("broadcaster");
});
socket.on("watcher", () => {
if (broadcaster) {
viewers[socket.id] = socket;
socket.emit("broadcaster", broadcaster);
}
});
socket.on("offer", ({ receiverId, offer }) => {
if (viewers[receiverId]) {
viewers[receiverId].emit("offer", { senderId: socket.id, offer });
}
});
socket.on("answer", ({ receiverId, answer }) => {
if (broadcaster === receiverId) {
io.to(receiverId).emit("answer", { senderId: socket.id, answer });
}
});
socket.on("ice-candidate", ({ receiverId, candidate }) => {
io.to(receiverId).emit("ice-candidate", { senderId: socket.id, candidate });
});
socket.on("disconnect", () => {
if (broadcaster === socket.id) {
broadcaster = null;
io.emit("broadcaster-disconnected");
} else {
delete viewers[socket.id];
}
});
});
server.listen(5000, () => console.log("Server running on port 5000"));
π Broadcaster.js
import React, { useEffect, useRef, useState } from "react";
import io from "socket.io-client";
const socket = io("http://localhost:5000");
const Broadcaster = () => {
const videoRef = useRef(null);
const [peerConnections, setPeerConnections] = useState({});
const ICE_SERVERS = [{ urls: "stun:stun.l.google.com:19302" }];
useEffect(() => {
navigator.mediaDevices.getUserMedia({ video: true, audio: true }).then((stream) => {
videoRef.current.srcObject = stream;
socket.emit("broadcaster");
socket.on("watcher", (viewerId) => {
const peerConnection = new RTCPeerConnection({ iceServers: ICE_SERVERS });
stream.getTracks().forEach((track) => peerConnection.addTrack(track, stream));
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
socket.emit("ice-candidate", { receiverId: viewerId, candidate: event.candidate });
}
};
peerConnection.createOffer().then((offer) => {
peerConnection.setLocalDescription(offer);
socket.emit("offer", { receiverId: viewerId, offer });
});
setPeerConnections((prev) => ({ ...prev, [viewerId]: peerConnection }));
});
socket.on("answer", ({ senderId, answer }) => {
peerConnections[senderId]?.setRemoteDescription(new RTCSessionDescription(answer));
});
socket.on("ice-candidate", ({ senderId, candidate }) => {
peerConnections[senderId]?.addIceCandidate(new RTCIceCandidate(candidate));
});
socket.on("broadcaster-disconnected", () => {
Object.values(peerConnections).forEach((pc) => pc.close());
setPeerConnections({});
});
});
return () => socket.disconnect();
}, []);
return (
<div>
<h1>Live Stream (Broadcaster)</h1>
<video ref={videoRef} autoPlay playsInline muted />
</div>
);
};
export default Broadcaster;
π Viewer.js
import React, { useEffect, useRef, useState } from "react";
import io from "socket.io-client";
const socket = io("http://localhost:5000");
const Viewer = () => {
const videoRef = useRef(null);
const [peerConnection, setPeerConnection] = useState(null);
const ICE_SERVERS = [{ urls: "stun:stun.l.google.com:19302" }];
useEffect(() => {
socket.emit("watcher");
socket.on("broadcaster", (broadcasterId) => {
const peerConnection = new RTCPeerConnection({ iceServers: ICE_SERVERS });
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
socket.emit("ice-candidate", { receiverId: broadcasterId, candidate: event.candidate });
}
};
peerConnection.ontrack = (event) => {
videoRef.current.srcObject = event.streams[0];
};
socket.on("offer", async ({ senderId, offer }) => {
await peerConnection.setRemoteDescription(new RTCSessionDescription(offer));
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
socket.emit("answer", { receiverId: senderId, answer });
});
socket.on("ice-candidate", ({ senderId, candidate }) => {
peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
});
setPeerConnection(peerConnection);
});
return () => socket.disconnect();
}, []);
return (
<div>
<h1>Live Stream (Viewer)</h1>
<video ref={videoRef} autoPlay playsInline />
</div>
);
};
export default Viewer;
- Start the WebRTC Server
node server.js
- Start the React App
npm start
- Open two browser tabs:
- One for the Broadcaster (Live Streamer)
- One for the Viewer
β
Broadcaster captures video/audio using getUserMedia()
β
Signaling via WebSockets to connect to viewers
β
WebRTC Peer Connection established
β
Viewers receive and display the live stream
- Recording the Stream (Save video files on the server)
- Adding Chat with RTCDataChannel
- Optimizing for Low Latency
WebRTC isn't just for web browsersβit works on mobile apps too! In this section, we'll explore how to use WebRTC in React Native (Expo) to build a real-time video calling app.
Unlike web browsers, where WebRTC is built-in, React Native requires external libraries to support WebRTC.
react-native-webrtc
- Provides WebRTC functionality in React Native.socket.io-client
- Used for signaling (SDP exchange).
- Get User Media β Access camera & microphone using WebRTC API.
- Establish Connection β Use WebSockets for signaling.
- Send/Receive Video & Audio β Use
RTCPeerConnection
. - Handle ICE Candidates β Ensure NAT traversal with STUN/TURN.
Run the following commands in your React Native (Expo) project:
npx expo install react-native-webrtc socket.io-client
π server.js
(Node.js WebSockets for signaling)
const express = require("express");
const http = require("http");
const { Server } = require("socket.io");
const cors = require("cors");
const app = express();
app.use(cors());
const server = http.createServer(app);
const io = new Server(server, {
cors: { origin: "*", methods: ["GET", "POST"] }
});
let users = {};
io.on("connection", (socket) => {
console.log("User connected:", socket.id);
socket.on("join", (room) => {
users[socket.id] = room;
socket.join(room);
socket.to(room).emit("new-user", socket.id);
});
socket.on("offer", ({ receiverId, offer }) => {
io.to(receiverId).emit("offer", { senderId: socket.id, offer });
});
socket.on("answer", ({ receiverId, answer }) => {
io.to(receiverId).emit("answer", { senderId: socket.id, answer });
});
socket.on("ice-candidate", ({ receiverId, candidate }) => {
io.to(receiverId).emit("ice-candidate", { senderId: socket.id, candidate });
});
socket.on("disconnect", () => {
socket.broadcast.emit("user-disconnected", socket.id);
delete users[socket.id];
});
});
server.listen(5000, () => console.log("Signaling server running on port 5000"));
π VideoCall.js
import React, { useEffect, useRef, useState } from "react";
import { View, Text, TouchableOpacity } from "react-native";
import { RTCPeerConnection, RTCView, mediaDevices, RTCIceCandidate, RTCSessionDescription } from "react-native-webrtc";
import io from "socket.io-client";
const socket = io("http://localhost:5000");
const ICE_SERVERS = [{ urls: "stun:stun.l.google.com:19302" }];
const VideoCall = () => {
const [localStream, setLocalStream] = useState(null);
const [remoteStream, setRemoteStream] = useState(null);
const peerConnection = useRef(null);
useEffect(() => {
startCall();
socket.on("new-user", async (userId) => {
const offer = await createOffer();
socket.emit("offer", { receiverId: userId, offer });
});
socket.on("offer", async ({ senderId, offer }) => {
await peerConnection.current.setRemoteDescription(new RTCSessionDescription(offer));
const answer = await peerConnection.current.createAnswer();
await peerConnection.current.setLocalDescription(answer);
socket.emit("answer", { receiverId: senderId, answer });
});
socket.on("answer", async ({ senderId, answer }) => {
await peerConnection.current.setRemoteDescription(new RTCSessionDescription(answer));
});
socket.on("ice-candidate", ({ senderId, candidate }) => {
peerConnection.current.addIceCandidate(new RTCIceCandidate(candidate));
});
return () => {
socket.disconnect();
if (peerConnection.current) peerConnection.current.close();
};
}, []);
const startCall = async () => {
peerConnection.current = new RTCPeerConnection({ iceServers: ICE_SERVERS });
const stream = await mediaDevices.getUserMedia({ video: true, audio: true });
setLocalStream(stream);
stream.getTracks().forEach((track) => peerConnection.current.addTrack(track, stream));
peerConnection.current.ontrack = (event) => {
setRemoteStream(event.streams[0]);
};
peerConnection.current.onicecandidate = (event) => {
if (event.candidate) {
socket.emit("ice-candidate", { candidate: event.candidate });
}
};
socket.emit("join", "room1");
};
const createOffer = async () => {
const offer = await peerConnection.current.createOffer();
await peerConnection.current.setLocalDescription(offer);
return offer;
};
return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<Text>React Native WebRTC Video Call</Text>
{localStream && <RTCView streamURL={localStream.toURL()} style={{ width: 200, height: 200 }} />}
{remoteStream && <RTCView streamURL={remoteStream.toURL()} style={{ width: 200, height: 200, marginTop: 20 }} />}
<TouchableOpacity onPress={startCall} style={{ marginTop: 20, padding: 10, backgroundColor: "blue" }}>
<Text style={{ color: "white" }}>Start Call</Text>
</TouchableOpacity>
</View>
);
};
export default VideoCall;
- Start the Signaling Server
node server.js
- Start Expo Project
npx expo start
- Test on Two Devices (or Two Emulator Windows)
- Open the app on two different phones or emulators.
- Start a video call and see the WebRTC connection in action!
β
Real-time Video Calling using WebRTC
β
Signaling with WebSockets
β
STUN Server for NAT Traversal
β
Mobile-Friendly UI using Expo
πΉ Implement Call End Functionality
πΉ Add Mute/Unmute & Camera Toggle
πΉ Use a TURN Server for Better Connectivity
πΉ Enable Group Video Calls with SFU (Mediasoup)
WebRTC isn't just for browsersβit can run on the server too! This is useful for:
β
Recording Calls on the Server
β
Live Streaming to YouTube/Twitch
β
Processing Media (e.g., Adding Watermarks, Transcoding)
Since WebRTC needs a media source, we replace the browser's camera/microphone with:
- FFmpeg β Simulates a media source (e.g., video file, RTSP stream).
- Puppeteer (Headless Chrome) β Can act as a hidden WebRTC client (optional).
- GStreamer/Mediasoup β Server-side SFU for media routing.
First, install WebRTC & media processing tools:
npm install express socket.io wrtc fluent-ffmpeg
sudo apt install ffmpeg # For Linux/macOS
π server.js
(Node.js WebRTC Signaling Server)
const express = require("express");
const http = require("http");
const { Server } = require("socket.io");
const wrtc = require("wrtc");
const app = express();
const server = http.createServer(app);
const io = new Server(server);
let clients = {};
io.on("connection", (socket) => {
console.log("Client connected:", socket.id);
socket.on("offer", async ({ senderId, offer }) => {
console.log("Received Offer:", offer);
const peerConnection = new wrtc.RTCPeerConnection();
clients[senderId] = peerConnection;
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
socket.emit("ice-candidate", { candidate: event.candidate });
}
};
peerConnection.ontrack = (event) => {
console.log("Received Media Stream!");
recordStream(event.streams[0]);
};
await peerConnection.setRemoteDescription(new wrtc.RTCSessionDescription(offer));
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
socket.emit("answer", { answer });
});
socket.on("ice-candidate", ({ candidate }) => {
clients[socket.id]?.addIceCandidate(new wrtc.RTCIceCandidate(candidate));
});
socket.on("disconnect", () => {
console.log("Client disconnected:", socket.id);
delete clients[socket.id];
});
});
server.listen(5000, () => console.log("WebRTC Server running on port 5000"));
π recordStream.js
const ffmpeg = require("fluent-ffmpeg");
const fs = require("fs");
function recordStream(mediaStream) {
console.log("Recording WebRTC stream...");
const videoFile = `./recordings/recording_${Date.now()}.mp4`;
const ffmpegProcess = ffmpeg()
.input(mediaStream)
.inputFormat("rtsp")
.output(videoFile)
.videoCodec("libx264")
.audioCodec("aac")
.on("end", () => console.log(`Recording saved: ${videoFile}`))
.on("error", (err) => console.error("FFmpeg Error:", err))
.run();
}
module.exports = { recordStream };
If we want WebRTC to receive a video file instead of a real webcam, we can use FFmpeg:
ffmpeg -re -i sample.mp4 -f rtp rtp://localhost:5004
This simulates a live video feed that WebRTC can consume.
π liveStream.js
const { spawn } = require("child_process");
function streamToYouTube(mediaStream, streamKey) {
const ffmpegProcess = spawn("ffmpeg", [
"-i", mediaStream,
"-f", "flv",
`rtmp://a.rtmp.youtube.com/live2/${streamKey}`
]);
ffmpegProcess.stderr.on("data", (data) => console.log("FFmpeg:", data.toString()));
}
module.exports = { streamToYouTube };
Now you can broadcast WebRTC calls live on YouTube! π₯
node server.js
Then test it with a WebRTC client like React, React Native, or even OBS Studio.
β
Server-Side WebRTC Peer Connection
β
WebRTC Stream Recording using FFmpeg
β
Live Streaming WebRTC to YouTube/Twitch
β
Handling WebRTC ICE Candidates in Node.js
πΉ Add TURN Server for NAT Traversal
πΉ Multi-Client Video Conferencing (SFU)
πΉ Transcode WebRTC Streams for Different Devices
π₯ Now you have a fully working WebRTC server with media recording!
WebRTC can be enhanced with AI/ML for:
β
Face Detection & Recognition (OpenCV, TensorFlow.js)
β
Background Blur & Virtual Backgrounds (BodyPix, MediaPipe)
β
Speech-to-Text & Translation (Whisper, Deepgram)
β
Gesture Recognition & Eye Tracking
npm install express socket.io wrtc @tensorflow/tfjs @tensorflow-models/body-pix
npm install fluent-ffmpeg opencv4nodejs
- wrtc β WebRTC in Node.js
- @tensorflow/tfjs & @tensorflow-models/body-pix β AI for background removal
- opencv4nodejs β Face detection & object tracking
π server.js
(WebRTC Server with AI Processing)
const express = require("express");
const http = require("http");
const { Server } = require("socket.io");
const wrtc = require("wrtc");
const cv = require("opencv4nodejs");
const { applyAIProcessing } = require("./aiProcessing");
const app = express();
const server = http.createServer(app);
const io = new Server(server);
let clients = {};
io.on("connection", (socket) => {
console.log("Client connected:", socket.id);
socket.on("offer", async ({ senderId, offer }) => {
console.log("Received Offer:", offer);
const peerConnection = new wrtc.RTCPeerConnection();
clients[senderId] = peerConnection;
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
socket.emit("ice-candidate", { candidate: event.candidate });
}
};
peerConnection.ontrack = (event) => {
console.log("Received Media Stream!");
applyAIProcessing(event.streams[0]); // π₯ Apply AI processing here
};
await peerConnection.setRemoteDescription(new wrtc.RTCSessionDescription(offer));
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
socket.emit("answer", { answer });
});
socket.on("ice-candidate", ({ candidate }) => {
clients[socket.id]?.addIceCandidate(new wrtc.RTCIceCandidate(candidate));
});
socket.on("disconnect", () => {
console.log("Client disconnected:", socket.id);
delete clients[socket.id];
});
});
server.listen(5000, () => console.log("WebRTC AI Server running on port 5000"));
π aiProcessing.js
(Apply AI on WebRTC Video)
const tf = require("@tensorflow/tfjs-node");
const bodyPix = require("@tensorflow-models/body-pix");
const cv = require("opencv4nodejs");
// Load AI models
async function loadModels() {
console.log("Loading AI Models...");
return await bodyPix.load();
}
async function applyAIProcessing(mediaStream) {
const model = await loadModels();
console.log("Applying AI Processing on WebRTC Stream...");
mediaStream.on("data", async (frameBuffer) => {
let frame = cv.imdecode(frameBuffer);
// Convert to Tensor & Apply BodyPix (Background Blur)
const tensor = tf.node.decodeImage(frameBuffer);
const segmentation = await model.segmentPerson(tensor);
// Apply Blur Effect on Background
if (segmentation) {
frame = frame.blur(new cv.Size(15, 15));
}
console.log("Processed Frame with AI!");
});
}
module.exports = { applyAIProcessing };
node server.js
β
Face & Object Detection with OpenCV
β
Background Blur with TensorFlow.js
β
Real-Time Video Processing on WebRTC Streams
πΉ Real-Time Hand Gesture Recognition (MediaPipe)
πΉ Live Captioning with AI Speech Recognition
πΉ Emotion Detection & Sentiment Analysis
π₯ Now you have WebRTC integrated with AI/ML!