Getting Started with WebRTC: Real‑Time P2P Communication in Browsers and Mobile Apps

This article introduces WebRTC, compares it with WebSocket, explains its architecture and core APIs, shows how to build a FastAPI‑based signaling server, demonstrates a full JavaScript P2P connection setup with TURN configuration, and provides code for screen sharing and audio/video controls.

Long Ge's Treasure Box
Long Ge's Treasure Box
Long Ge's Treasure Box
Getting Started with WebRTC: Real‑Time P2P Communication in Browsers and Mobile Apps

1. WebRTC Overview

WebRTC (Web Real‑Time Communication) is an open‑source project that enables browsers and mobile apps to exchange audio, video, screen, and data directly without plugins.

WebRTC Core Capabilities
┌─────────────────────────────────────────────────────────────┐
│  Audio Call   Video Call   Screen Share                     │
│  P2P Data    File Transfer   Game Communication          │
└─────────────────────────────────────────────────────────────┘

1.2 WebRTC vs WebSocket

Connection method: WebRTC uses P2P (may traverse TURN); WebSocket uses server relay.

Latency: WebRTC offers ultra‑low latency via direct connection; WebSocket has moderate latency.

Server load: WebRTC offloads after direct connection; WebSocket keeps all traffic on the server.

Typical use‑case: WebRTC for audio/video calls; WebSocket for chat and real‑time data.

NAT traversal: WebRTC requires ICE; WebSocket does not.

2. WebRTC Architecture

2.1 Overall Architecture

WebRTC Architecture
┌─────────────────────────────────────────────────────────────┐
│                     Web API                                 │
│  getUserMedia   RTCPeerConnection   RTCDataChannel        │
│        │            │                │                     │
│        ▼            ▼                ▼                     │
│                WebRTC Engine                               │
│   ICE   SRTP   DTLS   RTP   SDP                               │
│                Network (UDP/TCP)                             │
└─────────────────────────────────────────────────────────────┘

2.2 Core APIs

getUserMedia()

– acquire audio/video streams. RTCPeerConnection – manage the P2P connection. RTCDataChannel – send arbitrary data over the P2P link. getDisplayMedia() – capture screen for sharing.

3. Signaling Server

3.1 Role of the Signaling Server

Signaling Server Workflow
User A  →  Server  →  User B
   SDP Offer →            →   SDP Offer
   ← SDP Answer ←            ← SDP Answer
   ICE Candidates ↔ ICE Candidates
   Direct P2P connection established

3.2 Implementation with FastAPI and Socket.IO

from fastapi import FastAPI
from fastapi_socketio import SocketManager
import json

app = FastAPI()
sio = SocketManager(app, cors_allowed_origins="*")

@sio.on("join")
async def handle_join(sid, data):
    """User joins a room"""
    room = data.get("room")
    await sio.enter_room(sid, room)
    await sio.emit("user_joined", {"sid": sid}, room=room)

@sio.on("offer")
async def handle_offer(sid, data):
    """Forward SDP Offer"""
    await sio.emit("offer", {"sdp": data["sdp"], "from_sid": sid},
                   room=data["room"], skip_sid=sid)

@sio.on("answer")
async def handle_answer(sid, data):
    """Forward SDP Answer"""
    await sio.emit("answer", {"sdp": data["sdp"], "from_sid": sid},
                   room=data["room"], skip_sid=sid)

@sio.on("ice_candidate")
async def handle_ice_candidate(sid, data):
    """Forward ICE candidate"""
    await sio.emit("ice_candidate", {"candidate": data["candidate"], "room": data["room"]},
                   room=data["room"], skip_sid=sid)

@sio.on("leave")
async def handle_leave(sid, data):
    """User leaves"""
    room = data.get("room")
    await sio.leave_room(sid, room)
    await sio.emit("user_left", {"sid": sid}, room=room)

4. Establishing a P2P Connection

4.1 Full JavaScript Implementation

class WebRTCClient {
    constructor() {
        this.peerConnection = null;
        this.localStream = null;
        this.roomId = location.hash.slice(1);
    }

    async initialize() {
        // 1. Get local media
        this.localStream = await navigator.mediaDevices.getUserMedia({video:true, audio:true});
        document.getElementById('localVideo').srcObject = this.localStream;

        // 2. Connect to signaling server
        this.socket = io();

        // 3. Set up socket handlers
        this.setupSocketHandlers();

        // 4. Join room
        this.socket.emit('join', {room: this.roomId});
    }

    createPeerConnection() {
        // Create RTCPeerConnection
        this.peerConnection = new RTCPeerConnection({
            iceServers: [
                {urls: 'stun:stun.l.google.com:19302'},
                {urls: 'stun:stun1.l.google.com:19302'}
            ]
        });

        // Add local tracks
        this.localStream.getTracks().forEach(track => {
            this.peerConnection.addTrack(track, this.localStream);
        });

        // ICE candidate handling
        this.peerConnection.onicecandidate = (event) => {
            if (event.candidate) {
                this.socket.emit('ice_candidate', {
                    candidate: event.candidate,
                    room: this.roomId
                });
            }
        };

        // Remote stream handling
        this.peerConnection.ontrack = (event) => {
            document.getElementById('remoteVideo').srcObject = event.streams[0];
        };

        return this.peerConnection;
    }

    async call() {
        this.createPeerConnection();

        // Create SDP Offer
        const offer = await this.peerConnection.createOffer();
        await this.peerConnection.setLocalDescription(offer);

        // Send Offer
        this.socket.emit('offer', {sdp: offer, room: this.roomId});
    }

    async handleOffer(sdp) {
        this.createPeerConnection();
        await this.peerConnection.setRemoteDescription(new RTCSessionDescription(sdp));

        // Create Answer
        const answer = await this.peerConnection.createAnswer();
        await this.peerConnection.setLocalDescription(answer);

        // Send Answer
        this.socket.emit('answer', {sdp: answer, room: this.roomId});
    }

    async handleAnswer(sdp) {
        await this.peerConnection.setRemoteDescription(new RTCSessionDescription(sdp));
    }

    async handleIceCandidate(candidate) {
        try {
            await this.peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
        } catch (e) {
            console.error('Error adding ICE candidate:', e);
        }
    }

    setupSocketHandlers() {
        this.socket.on('user_joined', (data) => {
            console.log('User joined:', data.sid);
            if (data.sid !== this.socket.id) {
                this.call();
            }
        });

        this.socket.on('offer', (data) => {
            this.handleOffer(data.sdp);
        });

        this.socket.on('answer', (data) => {
            this.handleAnswer(data.sdp);
        });

        this.socket.on('ice_candidate', (data) => {
            this.handleIceCandidate(data.candidate);
        });

        this.socket.on('user_left', (data) => {
            console.log('User left:', data.sid);
        });
    }
}

4.2 TURN Server Configuration

const config = {
    iceServers: [
        // STUN servers
        {urls: 'stun:stun.l.google.com:19302'},
        {urls: 'stun:stun1.l.google.com:19302'},

        // TURN server (for strict NAT)
        {
            urls: 'turn:your-turn-server.com:3478',
            username: 'user',
            credential: 'password'
        }
    ]
};

5. Media Stream Handling

5.1 Screen Sharing

async function startScreenShare() {
    try {
        const screenStream = await navigator.mediaDevices.getDisplayMedia({
            video: {cursor: 'always'},
            audio: false
        });

        // Replace video track
        const videoTrack = screenStream.getVideoTracks()[0];
        const sender = peerConnection.getSenders().find(s => s.track.kind === 'video');
        if (sender) {
            await sender.replaceTrack(videoTrack);
        }

        // Handle stop sharing
        videoTrack.onended = () => {
            stopScreenShare();
        };

        document.getElementById('screenShareVideo').srcObject = screenStream;
    } catch (error) {
        console.error('Error sharing screen:', error);
    }
}

async function stopScreenShare() {
    // Restore camera
    const cameraStream = await navigator.mediaDevices.getUserMedia({video:true});
    const cameraTrack = cameraStream.getVideoTracks()[0];
    const sender = peerConnection.getSenders().find(s => s.track.kind === 'video');
    if (sender) {
        await sender.replaceTrack(cameraTrack);
    }
}

5.2 Audio/Video Control

// Mute / Unmute
function toggleAudio() {
    const audioTrack = localStream.getAudioTracks()[0];
    audioTrack.enabled = !audioTrack.enabled;
    document.getElementById('audioBtn').textContent =
        audioTrack.enabled ? 'Mute' : 'Unmute';
}

// Enable / Disable video
function toggleVideo() {
    const videoTrack = localStream.getVideoTracks()[0];
    videoTrack.enabled = !videoTrack.enabled;
    document.getElementById('videoBtn').textContent =
        videoTrack.enabled ? 'Stop Video' : 'Start Video';
}
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaScriptFastAPIReal-Time CommunicationP2PWebRTCTURNSignaling Server
Long Ge's Treasure Box
Written by

Long Ge's Treasure Box

I'm Long Ge, and this is my treasure chest—packed with cutting‑edge tech insights, career‑advancement tips, and a touch of relaxation you crave. Open it daily for a new surprise.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.