WebRTC : appel vidéo navigateur sans serveur — tutoriel 2026
📍 Article du cluster : Web 2026 sans framework lourd : HTMX, Hotwire, Alpine.js
Cet article fait partie du cluster Web moderne. Pour la vue d’ensemble des outils web légers en 2026, lire d’abord le pilier.
Introduction
Dans un cabinet médical à Abidjan, un praticien a besoin de consulter un patient à distance. Dans une PME à Dakar, le responsable support souhaite prendre en main le poste d’un client sans passer par un abonnement Zoom. Dans un centre de formation à Bamako, un formateur veut proposer des sessions individuelles directement depuis son application web maison. Dans chacun de ces scénarios professionnels, la réponse technique est la même : WebRTC, l’API navigateur qui établit des flux audio, vidéo et de données en pair-à-pair (P2P), sans que vos médias ne transitent par un serveur tiers.
WebRTC est standardisé par le W3C (spécification officielle : w3.org/TR/webrtc) et implémenté nativement dans Chrome, Firefox, Safari et Edge depuis 2017. En 2026, la couverture navigateur approche les 97 % sur mobile et desktop. Il n’y a rien à installer côté client : c’est du JavaScript pur, disponible sous window.RTCPeerConnection.
Pourquoi ne pas simplement déployer Jitsi Meet en selfhosté ? Jitsi est une excellente solution pour les réunions à plusieurs participants, mais elle embarque un serveur de média (Jitsi Videobridge) qui mixe les flux, consomme du CPU et de la bande passante serveur, et requière une infrastructure dimensionnée. Pour les appels professionnels en tête-à-tête — consultation, support, coaching — WebRTC P2P direct est structurellement moins cher : une fois la connexion établie, les paquets vidéo circulent directement entre les deux navigateurs. Votre serveur ne voit que la phase de négociation initiale, quelques centaines d’octets tout au plus.
Ce tutoriel vous guide pas à pas, de zéro, jusqu’à un prototype fonctionnel en HTML et JavaScript vanille : capture caméra/micro, connexion P2P, signaling minimal, configuration STUN/TURN, canal de données pour le chat texte professionnel, et enregistrement de session via MediaRecorder. Tout le code est testé sur Chrome 124 et Firefox 126.
Prérequis
- HTTPS obligatoire : WebRTC bloque l’accès à la caméra sur HTTP non sécurisé. En développement local,
localhostest l’unique exception autorisée. En production, un certificat TLS valide est non négociable. - Un éditeur de code : VS Code ou tout éditeur supportant JavaScript suffit.
- Node.js 20+ pour le micro-serveur de signaling WebSocket (paquet
ws). - Notions de base en JavaScript : Promises, async/await, manipulation du DOM.
- Temps estimé : environ 40 minutes pour suivre toutes les étapes jusqu’au prototype fonctionnel.
- Optionnel pour la production : un serveur Linux (Debian 12 ou Ubuntu 24.04) pour héberger le serveur TURN coturn si vous déployez derrière un NAT opérateur — situation commune en Afrique de l’Ouest.
Étape 1 — Architecture WebRTC : signaling, STUN et TURN
Avant d’écrire une seule ligne de code, il est indispensable de comprendre pourquoi WebRTC nécessite trois composants distincts pour établir une connexion. L’erreur classique des débutants est de croire que « P2P sans serveur » signifie zéro infrastructure — c’est inexact. WebRTC est P2P pour le flux média, mais il a besoin d’une phase de négociation initiale coordonnée par un serveur léger appelé serveur de signaling.
Le signaling est le canal par lequel Alice et Bob échangent des métadonnées avant de parler directement : leur description de session SDP (Session Description Protocol), qui liste les codecs audio/vidéo acceptés, la résolution, les paramètres de sécurité DTLS, et les candidats ICE (adresses réseau). WebRTC ne standardise pas le protocole de signaling — vous pouvez utiliser WebSocket, HTTP long-polling ou même copier-coller manuellement les SDP dans un terminal. Dans ce tutoriel, nous utilisons un petit serveur WebSocket écrit en Node.js.
Le STUN (Session Traversal Utilities for NAT, RFC 5389) permet à chaque pair de découvrir son adresse IP publique telle que vue depuis Internet. Quand votre ordinateur est derrière un routeur ou un partage de connexion mobile, votre adresse locale (192.168.x.x ou 10.x.x.x) est invisible depuis l’extérieur. Le serveur STUN répond simplement « ton IP publique est 41.82.x.x, port 51230 ». Cette information est encapsulée dans un candidat ICE de type srflx (server-reflexive). Google met à disposition gratuitement stun.l.google.com:19302, ce qui suffit pour les cas simples.
Le TURN (Traversal Using Relays around NAT, RFC 5766) est nécessaire quand le NAT est symétrique — c’est-à-dire quand le routeur bloque les connexions entrantes non initialisées par le réseau interne. Dans ce cas, même avec les adresses STUN, les deux pairs ne parviennent pas à se joindre directement. Le serveur TURN sert alors de relais : les médias y transitent, mais le serveur ne les déchiffre pas (ils sont chiffrés par DTLS-SRTP bout-en-bout). Derrière le CGNAT des opérateurs 4G (Orange, MTN, Moov, Wave…) en Afrique de l’Ouest, le TURN est pratiquement obligatoire. Nous verrons à l’étape 5 comment déployer coturn en quelques minutes.
Le flux complet ressemble à ceci : Alice ouvre la page → capture sa caméra → crée une offre SDP → l’envoie via le signaling → Bob la reçoit → répond avec sa réponse SDP → les deux échangent leurs candidats ICE → le protocole ICE choisit le meilleur chemin réseau → la connexion DTLS-SRTP s’établit → les flux audio/vidéo partent en P2P ou via TURN. Ce processus prend typiquement entre 1 et 4 secondes sur une bonne connexion.
Étape 2 — Capturer la caméra et le microphone avec getUserMedia
L’API getUserMedia est le point d’entrée pour accéder aux périphériques média de l’utilisateur. Elle retourne une Promise qui résout en un objet MediaStream contenant une ou plusieurs pistes audio et vidéo. Le navigateur affiche automatiquement une demande d’autorisation à l’utilisateur avant de donner accès — ce consentement explicite est une protection de vie privée intégrée au standard.
Pour une consultation médicale ou une session de support client, les contraintes de qualité média doivent être adaptées au contexte réseau. Voici comment démarrer avec des paramètres réalistes pour une connexion 4G africaine :
// Contraintes média adaptées au contexte professionnel et réseau limité
const contraintes = {
audio: {
echoCancellation: true, // Suppression d'écho, indispensable en appel
noiseSuppression: true, // Réduction du bruit ambiant
sampleRate: 48000 // Fréquence standard WebRTC
},
video: {
width: { ideal: 640, max: 1280 }, // 640p suffit pour le support; 1280 si réseau le permet
height: { ideal: 480, max: 720 },
frameRate: { ideal: 24, max: 30 } // 24fps correct pour la parole
}
};
let streamLocal;
async function demarrerCapture() {
try {
streamLocal = await navigator.mediaDevices.getUserMedia(contraintes);
// Afficher le flux local dans un élément video (muté pour éviter l'écho)
const videoLocal = document.getElementById('video-local');
videoLocal.srcObject = streamLocal;
videoLocal.muted = true; // IMPORTANT : toujours muter le flux local
videoLocal.play();
console.log('Capture démarrée :', streamLocal.getTracks().map(t => t.kind));
} catch (erreur) {
// NotAllowedError = l'utilisateur a refusé ou HTTPS manquant
// NotFoundError = pas de caméra trouvée
console.error('Erreur getUserMedia :', erreur.name, erreur.message);
gererErreurMedia(erreur);
}
}
function gererErreurMedia(erreur) {
if (erreur.name === 'NotAllowedError') {
afficherMessage('Autorisation refusée. Vérifiez les permissions navigateur et la connexion HTTPS.');
} else if (erreur.name === 'NotFoundError') {
afficherMessage('Aucune caméra détectée. Mode audio seul activé.');
demarrerCaptureAudioSeul(); // Fallback audio-only
}
}
Notez l’attribut muted positionné sur l’élément vidéo local : si vous oubliez ce détail, vous entendrez votre propre voix avec un léger décalage, ce qui est très gênant en contexte professionnel. La fonction gererErreurMedia illustre déjà le principe du fallback audio-only, critique en Afrique de l’Ouest où certains appareils mobiles d’entrée de gamme n’exposent pas de caméra via l’API du navigateur.
Étape 3 — Configurer RTCPeerConnection
L’objet RTCPeerConnection est le cœur de WebRTC. Il encapsule toute la logique ICE (négociation du chemin réseau), DTLS (handshake de sécurité), SRTP (chiffrement des médias) et RTP/RTCP (transport des flux). Sa configuration initiale recense les serveurs STUN et TURN à utiliser. C’est ici que vous définissez combien d’infrastructure réseau vous mettez en jeu.
La configuration minimale pour le développement local utilise uniquement le STUN Google. La configuration production que nous verrons à l’étape 5 ajoutera votre serveur TURN. Voici comment instancier le pair local, lui attacher le flux média, et configurer les gestionnaires d’événements essentiels :
const configICE = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' } // STUN public Google, gratuit
// En production, ajouter le TURN coturn ici (voir étape 5)
]
};
let connexionPair; // RTCPeerConnection globale
async function creerConnexionPair() {
connexionPair = new RTCPeerConnection(configICE);
// Attacher toutes les pistes du flux local à la connexion
streamLocal.getTracks().forEach(piste => {
connexionPair.addTrack(piste, streamLocal);
});
// Quand un candidat ICE est découvert localement, l'envoyer au pair distant
connexionPair.addEventListener('icecandidate', evenement => {
if (evenement.candidate) {
// Envoyer via le canal de signaling (WebSocket, voir étape 4)
envoyerSignaling({ type: 'ice-candidate', candidate: evenement.candidate });
}
});
// Quand le flux distant arrive, l'afficher dans l'élément video distant
connexionPair.addEventListener('track', evenement => {
const videoDistant = document.getElementById('video-distant');
if (!videoDistant.srcObject) {
videoDistant.srcObject = evenement.streams[0];
videoDistant.play();
}
});
// Surveiller l'état de la connexion ICE pour le debug et le feedback UI
connexionPair.addEventListener('iceconnectionstatechange', () => {
console.log('ICE state:', connexionPair.iceConnectionState);
// 'connected' ou 'completed' = appel établi
// 'failed' = vérifier TURN
// 'disconnected' = perte réseau temporaire (peut se récupérer)
});
return connexionPair;
}
L’événement track est celui que les débutants oublient le plus souvent : sans ce gestionnaire, votre application recevra bien les données du pair distant, mais ne les affichera jamais. L’événement iceconnectionstatechange est essentiel pour donner un feedback visuel à l’utilisateur : l’état failed doit déclencher un message d’erreur explicite invitant à vérifier la connexion.
Étape 4 — Signaling via un serveur WebSocket minimaliste
Maintenant que les deux pairs savent créer une RTCPeerConnection, ils doivent pouvoir s’échanger leurs descriptions SDP et candidats ICE. Le protocole de signaling n’étant pas défini par le standard WebRTC, nous allons implémenter le serveur le plus simple possible en Node.js avec le paquet ws. Ce serveur ne fait que relayer des messages JSON entre deux clients dans une « salle », sans stocker ni interpréter les contenus média.
Installez d’abord le serveur de signaling :
mkdir webrtc-signaling && cd webrtc-signaling
npm init -y
npm install ws # Serveur WebSocket léger pour Node.js
Créez ensuite le fichier serveur.js :
// serveur.js — Signaling WebSocket minimaliste pour WebRTC
const { WebSocketServer } = require('ws');
const PORT = process.env.PORT || 8080;
const wss = new WebSocketServer({ port: PORT });
// Salles : Map({ nomSalle => Set(clients WebSocket) })
const salles = new Map();
wss.on('connection', (ws) => {
let salleCourante = null;
ws.on('message', (donnees) => {
let message;
try {
message = JSON.parse(donnees);
} catch {
return; // Message malformé, ignorer
}
if (message.type === 'rejoindre') {
// Le client rejoint une salle d'appel identifiée par un identifiant de session
salleCourante = message.salle;
if (!salles.has(salleCourante)) {
salles.set(salleCourante, new Set());
}
salles.get(salleCourante).add(ws);
console.log(`Client rejoint salle: ${salleCourante} (${salles.get(salleCourante).size} participants)`);
} else {
// Relayer le message à tous les autres membres de la salle
const membres = salles.get(salleCourante);
if (membres) {
membres.forEach(client => {
if (client !== ws && client.readyState === 1) { // 1 = OPEN
client.send(JSON.stringify(message));
}
});
}
}
});
ws.on('close', () => {
if (salleCourante && salles.has(salleCourante)) {
salles.get(salleCourante).delete(ws);
if (salles.get(salleCourante).size === 0) {
salles.delete(salleCourante); // Nettoyage des salles vides
}
}
});
});
console.log(`Serveur de signaling WebRTC démarré sur ws://localhost:${PORT}`);
Démarrez le serveur avec node serveur.js. Côté client, la logique de signaling coordonne l’échange offre/réponse SDP : l’appelant crée une offre SDP avec connexionPair.createOffer(), la place comme description locale (setLocalDescription), l’envoie via WebSocket. Le receveur la pose comme description distante (setRemoteDescription), crée une réponse (createAnswer), la pose comme description locale, et renvoie la réponse. Enfin, chaque pair reçoit les candidats ICE de l’autre et les ajoute avec addIceCandidate. Ce protocole d’échange est défini dans la spécification JSEP (RFC 8829).
Étape 5 — STUN gratuit Google et serveur TURN coturn pour NAT
Le STUN Google couvre les cas où les deux pairs sont derrière des routeurs à NAT « plein-cône » ou « port-restreint ». Mais derrière un NAT symétrique — typiquement le cas des connexions 4G opérateurs en Afrique de l’Ouest — le STUN seul échoue. Le pair distant ne peut pas initier de connexion vers le port éphémère assigné par le NAT, car ce port change à chaque destination. Dans ce cas, il faut un serveur TURN qui relaie les paquets.
Installation de coturn sur Debian 12 ou Ubuntu 24.04 :
sudo apt update && sudo apt install coturn -y
# Activer coturn au démarrage
sudo systemctl enable coturn
Voici une configuration minimale et sécurisée dans /etc/turnserver.conf :
# /etc/turnserver.conf — Configuration coturn minimale
listening-port=3478
tls-listening-port=5349
# Remplacer par l'IP publique de votre VPS
external-ip=VOTRE_IP_PUBLIQUE
# Réalm d'authentification
realm=votredomaine.com
# Credentials TURN (utiliser --user pour créer plusieurs utilisateurs)
user=appel:MotDePasseFortIci
# Certificat TLS (Let's Encrypt recommandé)
cert=/etc/letsencrypt/live/votredomaine.com/fullchain.pem
pkey=/etc/letsencrypt/live/votredomaine.com/privkey.pem
# Sécurité : interdire les connexions UDP sortantes non-média
no-udp-relay
no-tcp-relay
# Logging
log-file=/var/log/coturn.log
Après sudo systemctl start coturn, ouvrez les ports 3478 (UDP/TCP) et 5349 (TLS) dans votre pare-feu. Côté client, ajoutez les credentials TURN à la configuration ICE :
const configICEProduction = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{
urls: [
'turn:votredomaine.com:3478?transport=udp',
'turn:votredomaine.com:3478?transport=tcp',
'turns:votredomaine.com:5349?transport=tcp' // TLS, plus robuste sur réseau mobile
],
username: 'appel',
credential: 'MotDePasseFortIci'
}
],
iceTransportPolicy: 'all' // Tenter P2P d'abord, TURN en fallback
};
Avec iceTransportPolicy: 'all', WebRTC essaie d’abord une connexion directe (moins de latence, zéro consommation TURN) et ne passe par le relais que si nécessaire. Sur un VPS basique à 5 €/mois chez Hetzner ou Contabo, coturn gère sans difficulté des dizaines d’appels simultanés — le traffic TURN pour un appel vidéo 640p représente environ 1-2 Mbps.
Étape 6 — Data channels pour la messagerie professionnelle
Les RTCDataChannel permettent d’échanger des données arbitraires (texte, JSON, fichiers binaires) entre les deux pairs, via le même canal P2P chiffré DTLS que les médias. Dans un contexte de support client ou de consultation médicale, un canal de texte intégré permet de partager un numéro de référence, une ordonnance, ou un lien de document sans sortir de l’interface. C’est plus simple et plus sécurisé que d’intégrer un outil de messagerie tiers.
Le canal doit être créé avant la génération de l’offre SDP, de sorte qu’il soit négocié dans la même connexion ICE :
let canalTexte; // RTCDataChannel global
function creerCanalDonnees() {
// L'appelant crée le canal ; le receveur le détecte via l'événement 'datachannel'
canalTexte = connexionPair.createDataChannel('messagerie-pro', {
ordered: true // Messages ordonnés (comme TCP), nécessaire pour le texte
});
configurerCanalTexte(canalTexte);
}
function configurerCanalTexte(canal) {
canal.addEventListener('open', () => {
console.log('Canal de données ouvert');
document.getElementById('zone-message').disabled = false;
});
canal.addEventListener('message', (evenement) => {
const donnees = JSON.parse(evenement.data);
afficherMessageRecu(donnees.texte, donnees.horodatage);
});
canal.addEventListener('close', () => {
document.getElementById('zone-message').disabled = true;
});
}
// Le receveur détecte le canal créé par l'appelant
connexionPair.addEventListener('datachannel', (evenement) => {
canalTexte = evenement.channel;
configurerCanalTexte(canalTexte);
});
function envoyerMessage(texte) {
if (canalTexte && canalTexte.readyState === 'open') {
canalTexte.send(JSON.stringify({
texte: texte,
horodatage: new Date().toISOString()
}));
}
}
Notez que seul l’appelant appelle createDataChannel : le receveur détecte automatiquement le canal via l’événement datachannel sur la connexion. Si vous omettez ce gestionnaire côté receveur, le canal s’ouvrira sans jamais être accessible — une source classique de bug silencieux.
Étape 7 — Enregistrement de session avec MediaRecorder
Dans un contexte de consultation médicale à distance ou de formation professionnelle, l’enregistrement de la session peut être requis pour archivage ou compte-rendu. L’API MediaRecorder du navigateur enregistre n’importe quel MediaStream, y compris celui reçu de l’interlocuteur distant. Attention : l’enregistrement doit être informé et consenti — dans un cadre médical ou professionnel, indiquez clairement à l’utilisateur que la session est enregistrée.
let enregistreur; // MediaRecorder
const segmentsAudio = []; // Tableau de Blob
function demarrerEnregistrement(stream) {
// Vérifier le format supporté selon le navigateur
const format = MediaRecorder.isTypeSupported('video/webm;codecs=vp9,opus')
? 'video/webm;codecs=vp9,opus'
: 'video/webm'; // Fallback générique
enregistreur = new MediaRecorder(stream, { mimeType: format });
enregistreur.addEventListener('dataavailable', (evenement) => {
if (evenement.data.size > 0) {
segmentsAudio.push(evenement.data); // Accumule les segments
}
});
enregistreur.addEventListener('stop', () => {
const blob = new Blob(segmentsAudio, { type: format });
const url = URL.createObjectURL(blob);
// Proposer le téléchargement à l'utilisateur
const lien = document.createElement('a');
lien.href = url;
lien.download = `session-${new Date().toISOString().slice(0,10)}.webm`;
lien.click();
URL.revokeObjectURL(url); // Libérer la mémoire
segmentsAudio.length = 0; // Réinitialiser
});
// Produire un segment toutes les 5 secondes (réduit la perte en cas de crash)
enregistreur.start(5000);
console.log('Enregistrement démarré au format :', format);
}
function arreterEnregistrement() {
if (enregistreur && enregistreur.state !== 'inactive') {
enregistreur.stop();
}
}
L’intervalle de 5000 ms passé à start() force la génération d’un événement dataavailable toutes les 5 secondes. Sans cet intervalle, vous obtenez un seul gros segment à la fin, ce qui signifie qu’un crash en cours d’enregistrement perd toute la session. Avec des segments réguliers, vous pouvez aussi implémenter un upload progressif vers un stockage S3 ou MinIO si l’espace disque local est limité.
Erreurs fréquentes
| Erreur | Cause | Solution |
|---|---|---|
NotAllowedError |
Page servée en HTTP (non-localhost) ou permission refusée | Servir en HTTPS ; vérifier les permissions navigateur dans la barre d’adresse |
iceConnectionState: failed |
NAT symétrique (CGNAT 4G) sans serveur TURN | Déployer coturn et ajouter les credentials TURN à la config ICE |
| Flux distant jamais affiché | Gestionnaire d’événement track absent ou srcObject non assigné |
Ajouter addEventListener('track', ...) avant createOffer() |
Offre SDP refusée (InvalidStateError) |
setLocalDescription appelé deux fois ou après setRemoteDescription |
Respecter l’ordre : createOffer → setLocal → envoyer → recevoir → setRemote → createAnswer |
| Echo dans l’appel | Flux local non muté dans l’élément vidéo | Ajouter videoLocal.muted = true ; activer echoCancellation dans les contraintes |
| Canal de données jamais ouvert | Canal créé après l’offre, ou gestionnaire datachannel absent côté receveur |
Créer le canal AVANT createOffer() ; ajouter le gestionnaire datachannel côté receveur |
| Enregistrement vide (fichier 0 octet) | MediaRecorder.stop() appelé avant le premier événement dataavailable |
Passer un intervalle à start(5000) ; appeler stop() avec un délai minimal de 500 ms après le début |
Adaptation au contexte ouest-africain
Déployer WebRTC en Afrique de l’Ouest implique de prendre en compte plusieurs réalités réseau qui diffèrent significativement d’un contexte européen ou nord-américain. Comprendre ces contraintes à l’avance évite des heures de débogage frustrant en production.
Le CGNAT des opérateurs 4G est la contrainte numéro un. Orange Sénégal, MTN Côte d’Ivoire, Moov Africa, Telecel au Burkina — tous ces opérateurs mutualisent des adresses IP publiques entre des milliers d’abonnés via du NAT à l’échelle opérateur (CGNAT). Derrière un CGNAT, le STUN seul échoue systématiquement car le NAT est symétrique ou de type « port-dépendant ». Le serveur TURN n’est pas optionnel en production — il est obligatoire. Un VPS à 5-7 €/mois chez Hetzner (Frankfurt) ou OVH Cloud (Roubaix) avec coturn couvre sans problème 50 à 100 utilisateurs simultanés pour des appels 1-to-1. La bande passante server-side reste raisonnable parce que le TURN n’est sollicité que quand la connexion directe échoue : en pratique, 40 à 60 % des appels passent en P2P direct même derrière du 4G, selon l’opérateur et la zone.
Le fallback audio-only est une fonctionnalité, pas un cas d’erreur. En zone péri-urbaine ou en déplacement, la bande passante peut descendre à 512 kbps ou moins. Un appel vidéo 640p consomme 800 kbps à 1.5 Mbps ; un appel audio Opus consomme 32 à 64 kbps. La différence est considérable. Implémentez dès le départ un bouton « Mode audio seul » qui désactive la piste vidéo via stream.getVideoTracks()[0].enabled = false : le flux continue, seule la bande passante vidéo est économisée. L’utilisateur peut basculer à tout moment sans raccrocher.
L’économie de bande passante serveur est l’argument décisif pour choisir WebRTC P2P. Avec Jitsi ou tout autre serveur de média centralisé, chaque octet du flux vidéo de l’appelant transite par votre VPS avant d’arriver au receveur : le serveur consomme en entrée ET en sortie. Avec WebRTC P2P, votre serveur ne voit que les messages de signaling, quelques kilo-octets au total. Pour une PME qui propose 200 consultations clients par mois de 30 minutes chacune, la différence de facture VPS peut être substantielle.
Adaptation du codec vidéo. Depuis Chrome 106 et Firefox 120, le codec AV1 est disponible en WebRTC. AV1 offre une qualité supérieure à VP8 et VP9 à débit égal, ce qui est avantageux sur les liaisons lentes. Cependant, l’encodage AV1 est gourmand en CPU, et un smartphone d’entrée de gamme (Tecno, Itel, Infinix) peut peiner. VP8 reste le codec le plus compatible et le moins exigeant en CPU — c’est le choix par défaut le plus sûr pour 2026 dans un contexte ouest-africain où le parc mobile est hétérogène.
Les coupures électriques et les changements réseau. Un utilisateur qui bascule du WiFi au 4G en cours d’appel provoque un changement d’adresse IP, ce qui interrompt la connexion ICE. WebRTC 1.0 supporte le ICE restart : l’appelant peut générer une nouvelle offre SDP avec l’option { iceRestart: true } pour rénégocier le chemin réseau sans raccrocher. Implémentez un détecteur sur iceConnectionState === 'disconnected' qui tente un ICE restart après 3 secondes : cela couvre la plupart des coupures fugitives sans intervention de l’utilisateur.
Tutoriels frères
- HTMX : formulaires dynamiques sans JavaScript — intégrer une interface de pré-appel WebRTC dans une page HTMX sans écrire de JS supplémentaire pour la navigation
- Alpine.js : composants réactifs directement en HTML — gérer l’état de l’interface d’appel (bouton mute, caméra on/off, indicateur de connexion) avec Alpine.js sans framework lourd
- WebSocket temps réel avec Node.js — approfondir le serveur de signaling WebSocket pour gérer salles multiples, authentification JWT et reconnexion automatique
Pour aller plus loin
- 🔞 Retour au pilier : Web 2026 sans framework lourd : HTMX, Hotwire, Alpine.js
- webrtc.org — site officiel du projet WebRTC, maintained par Google, avec exemples interactifs et documentation technique
- W3C WebRTC 1.0 Specification — spécification normative complète de l’API
RTCPeerConnection - MDN WebRTC API — référence MDN en français, la plus complète pour les développeurs francophones
- RFC 8829 — JSEP — spécification du protocole de signaling SDP pour WebRTC
- Tutoriel suivant conseillé dans le cluster : Hotwire Turbo Streams avec Rails 7 — intégrer WebRTC dans une application Rails existante
FAQ
WebRTC est-il vraiment « sans serveur » ?
Non, pas totalement. WebRTC est P2P pour le transport des médias une fois la connexion établie. Mais il nécessite un serveur de signaling pour échanger les métadonnées initiales (SDP, candidats ICE), et souvent un serveur TURN pour traverser les NAT symétriques. Ce que l’on gagne, c’est que les flux audio/vidéo eux-mêmes ne transitent pas par votre infrastructure — à condition que la connexion directe réussisse.
WebRTC est-il chiffré ?
Oui, obligatoirement. Le standard WebRTC spécifie que tous les médias doivent être chiffrés via DTLS-SRTP. Le canal de données est chiffré via DTLS. Il n’existe pas d’option pour désactiver le chiffrement — c’est intégré au protocole. Le serveur TURN, quand il est utilisé, voit passer des paquets chiffrés qu’il ne peut pas déchiffrer.
Combien coûte un serveur TURN coturn ?
Un VPS 1 vCPU / 2 Go RAM / 20 Go SSD à environ 4-6 €/mois chez Hetzner, Contabo ou OVH Cloud est largement suffisant pour 50 à 100 appels 1-to-1 simultanés (seuls les appels en mode TURN consomment de la bande passante serveur, pas les appels P2P directs). Des services TURN managements existent aussi (Twilio Network Traversal, Metered) avec facturation à la minute, utiles pour les très faibles volumes.
WebRTC fonctionne-t-il sur les smartphones Android d’entrée de gamme ?
Oui, à condition d’utiliser Chrome ou Firefox Mobile. Chrome Mobile inclut WebRTC depuis la version 28, Firefox Mobile depuis la version 62. Sur les appareils Tecno, Itel ou Infinix sous Android 10+, le navigateur Chrome fourni par le fabricant supporte WebRTC complètement. Le point d’attention est la capacité CPU pour l’encodage vidéo : privilégier VP8 640p au lieu de VP9 ou H.264 pour les appareils de moins de 2 Go de RAM.
Peut-on utiliser WebRTC pour plus de deux participants ?
Techniquement oui, en créant une RTCPeerConnection par paire de participants (topologie mesh). Mais le nombre de connexions croît en O(n²) : 3 participants = 3 connexions, 5 participants = 10 connexions, 10 participants = 45 connexions. Au-delà de 4-5 participants, la charge CPU et réseau sur chaque client devient prohibitive. Pour les réunions multi-participants, une architecture SFU (Selective Forwarding Unit) comme mediasoup ou LiveKit est recommandée — mais ce n’est plus du P2P pur.
Comment tester WebRTC en local sans HTTPS ?
localhost est la seule origin non-HTTPS autorisée par les navigateurs pour accéder à getUserMedia et RTCPeerConnection. Servez votre fichier HTML depuis http://localhost:3000 avec n’importe quel serveur local (Live Server dans VS Code, python -m http.server, npx serve). Pour tester entre deux appareils sur le même réseau local, générez un certificat auto-signé avec mkcert et servez en HTTPS local.
Quels sont les usages professionnels recommandés pour WebRTC ?
WebRTC se prête particulièrement bien aux appels professionnels en tête-à-tête : consultations médicales à distance (télémédecine), support client technique avec partage d’écran, sessions de formation individuelle, entretiens de recrutement, coaching professionnel. Ce sont des cas d’usage à valeur ajoutée élevée où la qualité audio/vidéo et la sécurité du chiffrement bout-en-bout sont primordiales, et où le volume d’appels reste modéré (1-to-1, quelques dizaines par jour).
Site réalisé par [ITS] ITSkillsCenter — Formation et ressources IT pour l’Afrique de l’Ouest