⏱️ Lectura: 17 min
Dos navegadores pueden intercambiar video, audio y archivos sin que un solo byte de esa conversación pase por un servidor central: eso es lo que hace posible WebRTC. La tecnología nació en Google en 2011, se convirtió en estándar del W3C y el IETF, y hoy corre nativamente en Chrome, Firefox, Safari y Edge sin plugins ni instalaciones adicionales.
📑 En este artículo
- TL;DR
- Qué es WebRTC y por qué importa
- Cómo funciona WebRTC en detalle
- Ejemplos prácticos: de la videollamada mínima al canal de datos
- Cómo empezar: montar un servidor de señalización mínimo
- Casos de uso reales
- Errores comunes y buenas prácticas
- Comparativa con alternativas
- Profundizando: arquitecturas de producción
- Preguntas frecuentes
- Referencias
La clave está en tres piezas que trabajan juntas: los servidores STUN y TURN que resuelven el problema de las redes NAT, el protocolo ICE que prueba rutas de red hasta encontrar la más directa, y SDP, el formato de texto que describe qué códecs y parámetros soporta cada extremo antes de conectar.
TL;DR
- WebRTC conecta dos navegadores directamente con RTCPeerConnection, sin plugins ni apps nativas.
- STUN descubre la IP pública detrás de un NAT; TURN retransmite el tráfico cuando la ruta directa no existe.
- ICE prueba todos los candidatos de red disponibles y elige automáticamente la ruta más rápida.
- SDP describe en texto plano los códecs y parámetros que cada navegador soporta antes de conectar.
- RTCDataChannel permite enviar archivos y datos arbitrarios por el mismo canal cifrado que el video.
- DTLS-SRTP cifra todo el tráfico de WebRTC de forma obligatoria, sin opción de desactivarlo.
- chrome://webrtc-internals muestra en vivo qué candidato ICE ganó la negociación de una llamada.
Qué es WebRTC y por qué importa
WebRTC (Web Real-Time Communication) es un conjunto de APIs de JavaScript y un stack de protocolos de red que permite a dos navegadores establecer una conexión directa para intercambiar audio, video y datos arbitrarios, sin necesidad de un plugin, una app nativa ni, en el caso ideal, un servidor que retransmita el contenido. El proyecto arrancó en Google como código abierto y desde entonces la especificación se mantiene conjuntamente entre el W3C (la API para el navegador) y el IETF (los protocolos de red subyacentes).
Antes de WebRTC, cualquier videollamada en el navegador dependía de plugins como Flash, o de que toda la conversación pasara por un servidor central que reenviaba cada paquete de audio y video. Eso tiene un costo real: cada minuto de llamada consume ancho de banda del servidor, y ese ancho de banda hay que pagarlo. WebRTC resuelve ese problema estableciendo, siempre que la red lo permite, una ruta P2P (peer-to-peer) directa entre los dos navegadores. El servidor solo interviene al principio, para coordinar la negociación, y después puede desconectarse sin que la llamada se corte.
La importancia de WebRTC no es solo de costos: también es de latencia y privacidad. Una ruta P2P directa elimina un salto de red completo, lo que reduce la latencia en videollamadas y en aplicaciones sensibles como el control remoto de dispositivos o el cloud gaming. Y en términos de privacidad, si el servidor de señalización nunca ve el contenido de audio o video, solo coordina metadatos de conexión, la superficie de exposición es mucho menor que la de un sistema centralizado.
Cómo funciona WebRTC en detalle
Una conexión WebRTC se arma en tres fases que ocurren, en gran parte, en paralelo: señalización, negociación ICE y establecimiento del canal seguro.
Señalización: el paso que WebRTC no estandariza
Antes de que dos navegadores puedan hablar entre sí necesitan intercambiar cierta información: qué códecs soportan, qué resoluciones prefieren, y las direcciones de red por donde intentar conectar. Ese intercambio inicial se llama señalización, y ocurre curiosamente por fuera de WebRTC: la especificación deja sin definir el transporte de señalización, así que cada aplicación elige el suyo (WebSocket, HTTP, o incluso un mensaje copiado a mano para una prueba). Lo único que viaja por ese canal son mensajes SDP (Session Description Protocol, RFC 8866), un formato de texto plano que describe la sesión: códecs disponibles, resolución y parámetros de cifrado.
El intercambio sigue el patrón offer/answer: el navegador que inicia la llamada genera un offer y lo manda por el canal de señalización; el otro extremo responde con un answer. Recién después de ese intercambio ambos navegadores saben qué parámetros va a usar la conexión.
ICE: encontrar la ruta de red
Con el SDP ya negociado, cada navegador arranca la recolección de candidatos ICE (Interactive Connectivity Establishment, RFC 8445): direcciones de red por donde intentaría conectar con el otro extremo. Hay tres tipos.
- Candidato host: la IP local de la máquina, directamente en la red LAN.
- Candidato server-reflexive: la IP pública que ve el mundo exterior, descubierta preguntándole a un servidor STUN desde qué dirección y puerto llega el tráfico.
- Candidato relay: una dirección en un servidor TURN que acepta retransmitir el tráfico cuando ninguna ruta directa funciona.
Cada navegador manda su lista de candidatos al otro por el canal de señalización, y ambos ejecutan connectivity checks: prueban cada combinación de candidato local y remoto hasta encontrar un par que funcione. Si existe una ruta directa, ICE la elige por ser la de menor latencia. Solo si todas las rutas directas fallan, algo típico en redes con NAT simétrico o firewalls corporativos restrictivos, ICE cae al candidato relay vía TURN.
sequenceDiagram
participant A as Navegador A
participant S as Servidor de senalizacion
participant B as Navegador B
A->>S: envia offer SDP
S-->>B: reenvia offer
B->>S: envia answer SDP
S-->>A: reenvia answer
A->>B: intercambia candidatos ICE
Note over A,B: conexion P2P establecida, media fluye directo
DTLS-SRTP: cifrado obligatorio, no opcional
Una vez que ICE eligió una ruta, WebRTC negocia una capa de cifrado antes de mandar el primer byte de media: DTLS (Datagram TLS) para el handshake de claves, y SRTP para cifrar el audio y video en tránsito. Esta capa está definida en la arquitectura de seguridad de WebRTC (RFC 8827) y es obligatoria: no existe un modo sin cifrar en la especificación, ni siquiera para pruebas en una red local. Es una decisión de diseño deliberada: como WebRTC corre en el navegador, cualquier tráfico sin cifrar sería trivialmente interceptable por cualquier proxy de red intermedio.
Ejemplos prácticos: de la videollamada mínima al canal de datos
Ejemplo 1: capturar la cámara y mostrarla en pantalla
El primer paso de cualquier app WebRTC, incluso antes de conectar con otro navegador, es pedir permiso para usar cámara y micrófono con getUserMedia.
const videoPreview = document.querySelector("#video-local");
async function iniciarCamara() {
const stream = await navigator.mediaDevices.getUserMedia({
video: { width: 640, height: 480 },
audio: true,
});
videoPreview.srcObject = stream;
return stream;
}
iniciarCamara();
Este bloque abre el selector de permisos del navegador, captura video de 640×480 y audio del micrófono, y los asigna al elemento <video> con id video-local para mostrar el preview local. Nada de esto involucra todavía a otro navegador: es puro MediaDevices.
Ejemplo 2: negociar la conexión con RTCPeerConnection
Con el stream local en mano, el siguiente paso es crear un RTCPeerConnection, agregarle las pistas de audio y video, y manejar el intercambio offer/answer a través de un servidor de señalización, acá representado como signalingSocket, un WebSocket cualquiera.
const ICE_SERVERS = {
iceServers: [
{ urls: "stun:stun.l.google.com:19302" },
{
urls: "turn:turn.miapp.com:3478",
username: "usuarioTurn",
credential: "credencialTurn",
},
],
};
const peerConnection = new RTCPeerConnection(ICE_SERVERS);
stream.getTracks().forEach((track) => peerConnection.addTrack(track, stream));
peerConnection.onicecandidate = (evento) => {
if (evento.candidate) {
signalingSocket.send(JSON.stringify({ tipo: "candidato", candidato: evento.candidate }));
}
};
peerConnection.ontrack = (evento) => {
videoRemoto.srcObject = evento.streams[0];
};
async function iniciarLlamada() {
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
signalingSocket.send(JSON.stringify({ tipo: "offer", sdp: offer }));
}
Este código registra un servidor STUN público de Google y un servidor TURN propio como respaldo, agrega las pistas del stream local a la conexión, y define qué hacer cuando llegan candidatos ICE propios (mandarlos al otro extremo) o pistas remotas (mostrarlas en un segundo <video>). iniciarLlamada() genera el offer y lo manda por el canal de señalización. El otro navegador respondería con un answer siguiendo el mismo patrón, y ambos llamarían a setRemoteDescription con el SDP recibido.
Ejemplo 3: transferir un archivo con RTCDataChannel
WebRTC no es solo audio y video: el mismo RTCPeerConnection puede abrir un canal de datos arbitrario, cifrado con el mismo DTLS y transportado sobre SCTP.
const canalArchivo = peerConnection.createDataChannel("transferencia-archivo");
canalArchivo.binaryType = "arraybuffer";
canalArchivo.onopen = () => console.log("canal de datos listo");
async function enviarArchivo(archivo) {
const buffer = await archivo.arrayBuffer();
const tamanoChunk = 16 * 1024;
for (let offset = 0; offset < buffer.byteLength; offset += tamanoChunk) {
canalArchivo.send(buffer.slice(offset, offset + tamanoChunk));
}
}
Este ejemplo trocea un archivo en bloques de 16 KB, el tamaño recomendado para evitar saturar el buffer interno del canal, y los manda uno por uno apenas el canal reporta estado open. Herramientas de transferencia P2P que evitan subir el archivo a un servidor intermedio se basan exactamente en este mecanismo.
Cómo empezar: montar un servidor de señalización mínimo
WebRTC no define el transporte de señalización, así que hace falta escribir uno. El más simple posible es un servidor WebSocket en Node.js que reenvía mensajes entre los clientes conectados.
npm install ws
// servidor-senalizacion.js
const { WebSocketServer } = require("ws");
const servidor = new WebSocketServer({ port: 8080 });
const clientes = new Set();
servidor.on("connection", (socket) => {
clientes.add(socket);
socket.on("message", (mensaje) => {
for (const otroCliente of clientes) {
if (otroCliente !== socket && otroCliente.readyState === 1) {
otroCliente.send(mensaje.toString());
}
}
});
socket.on("close", () => clientes.delete(socket));
});
Con node servidor-senalizacion.js corriendo en el puerto 8080, cualquier mensaje que un cliente mande (offer, answer, o un candidato ICE) se reenvía automáticamente al resto de los clientes conectados. Para producción hace falta agregar salas y autenticación, pero esta base de menos de 20 líneas alcanza para probar el flujo completo offer/answer/ICE entre dos pestañas del navegador.
Para el servidor TURN, la opción open source más usada es coturn, que se instala con apt install coturn en Debian/Ubuntu y expone tanto STUN como TURN desde el mismo binario.
Casos de uso reales
La aplicación más visible de WebRTC son los clientes web de videollamadas: Google Meet, y las versiones web de Zoom y Microsoft Teams corren su transporte de media sobre WebRTC, aunque agregan una capa de servidores propia para las llamadas grupales grandes. El uso real va mucho más allá del video.
- Telemedicina: consultas médicas remotas donde la latencia baja y el cifrado obligatorio son requisitos, no extras.
- Cloud gaming y control remoto: streaming de input de teclado y mouse con latencia mínima usando
RTCDataChannel. - Transferencia de archivos P2P: envío directo de archivos entre navegadores sin subirlos a ningún servidor intermedio.
- Streaming de dispositivos IoT: cámaras y robots que transmiten video directamente al navegador del operador.
- Aplicaciones descentralizadas: mensajería y colaboración P2P que buscan minimizar la dependencia de infraestructura central.
Errores comunes y buenas prácticas
⚠️ Ojo: getUserMedia solo funciona en un contexto seguro (HTTPS) o en localhost. Servir una demo de WebRTC por HTTP plano desde una IP de red falla silenciosamente, sin aviso claro del navegador.
El error más frecuente al debutar con WebRTC es asumir que la conexión P2P siempre se establece. En redes con NAT simétrico, comunes en redes corporativas y algunos proveedores móviles, ningún candidato server-reflexive funciona, y la única opción es caer al relay TURN. Si la aplicación no configura un servidor TURN, esos usuarios simplemente no logran conectar, sin ningún mensaje de error evidente más allá de que el estado de ICE nunca llega a connected.
Otro error común es no verificar el estado real de la conexión antes de asumir que ya está conectada. Escuchar oniceconnectionstatechange es la forma correcta.
peerConnection.oniceconnectionstatechange = () => {
console.log("estado ICE:", peerConnection.iceConnectionState);
};
Cuando ese estado llega a "connected" o "completed", la ruta ya está elegida y la media fluye. Para confirmarlo con más detalle, chrome://webrtc-internals en Chrome (o about:webrtc en Firefox) muestra en vivo qué candidato ganó la negociación, cuántos bytes se enviaron y si la ruta es directa o vía TURN. También se puede consultar programáticamente con peerConnection.getStats(), que devuelve un reporte con el par de candidatos activo y su marca nominated: true.
Otros gotchas frecuentes: los navegadores bloquean el autoplay de video con audio si el elemento no tiene el atributo muted hasta que haya interacción del usuario; olvidar cerrar el RTCPeerConnection con .close() y detener las pistas con track.stop() deja la cámara encendida; y asumir que la topología P2P (mesh) escala a videollamadas grupales grandes: el ancho de banda de subida crece con cada participante adicional, así que a partir de 4 a 6 personas conviene migrar a una arquitectura con SFU.
Comparativa con alternativas
| Opción | Cuándo usarla | Ventaja | Limitación |
|---|---|---|---|
| WebRTC | Video, audio o datos en tiempo real con latencia mínima | Ruta P2P directa, cifrado obligatorio, sin plugins | Señalización sin estandarizar, NAT traversal complejo |
| WebSocket | Mensajería bidireccional simple, chat, notificaciones | Fácil de implementar, un solo servidor central | Todo el tráfico pasa siempre por el servidor |
| HTTP long polling | Compatibilidad con infraestructura vieja o proxies restrictivos | Funciona detrás de casi cualquier firewall | Latencia alta, overhead de reconexión constante |
| QUIC / HTTP/3 puro | Transferencias cliente-servidor de baja latencia sin necesidad de P2P | Multiplexado sin head-of-line blocking | No resuelve conexión directa entre dos navegadores |
Profundizando: arquitecturas de producción
Una llamada P2P pura (topología mesh) funciona bien para dos o tres participantes: cada navegador manda su stream directamente a todos los demás. El costo de subida crece con cada participante adicional, porque cada navegador codifica y envía su propio video una vez por cada otro participante en la llamada. Por eso, a partir de cierto tamaño, las aplicaciones de producción migran a una SFU (Selective Forwarding Unit): un servidor que recibe un solo stream de cada participante y lo reenvía sin decodificarlo a todos los demás. Existe una tercera variante, la MCU (Multipoint Control Unit), que sí decodifica y mezcla todos los streams en uno solo antes de reenviarlo, a costa de mucho más cómputo en el servidor.
flowchart LR
subgraph Mesh["Topologia mesh: P2P puro"]
P1["Participante 1"] --- P2["Participante 2"]
P2 --- P3["Participante 3"]
P1 --- P3
end
subgraph SFU["Topologia SFU: servidor de medios"]
Q1["Participante 1"] --> R["Servidor SFU"]
Q2["Participante 2"] --> R
Q3["Participante 3"] --> R
R --> Q1
R --> Q2
R --> Q3
end
Otro componente avanzado es el control de congestión: WebRTC ajusta dinámicamente el bitrate de video según las condiciones de red usando algoritmos como GCC (Google Congestion Control) del lado del emisor y REMB (Receiver Estimated Maximum Bitrate) como feedback del receptor. Cuando una app soporta simulcast, el navegador emisor codifica el mismo video en 2 o 3 resoluciones simultáneas, y la SFU decide cuál reenviar a cada receptor según su ancho de banda disponible, sin recodificar nada.
flowchart TD
A["Navegador A"] --> B["Servidor STUN"]
A --> C["Servidor TURN"]
D["Navegador B"] --> B
D --> C
A -.->|"candidato P2P directo"| D
A -.->|"candidato via relay"| C
C -.->|"relay de medios"| D
subgraph ICE["Descubrimiento de rutas ICE"]
B
C
end
💡 Tip: las credenciales de un servidor TURN no deberían ser estáticas. La práctica recomendada es generar usuario y contraseña de corta duración (por ejemplo con HMAC sobre un timestamp) para cada sesión, así una credencial filtrada deja de servir en minutos.
WebRTC no es la respuesta correcta para todo. Para transmitir un stream de un emisor a miles de espectadores, tecnologías como HLS sobre CDN siguen siendo más simples y baratas de escalar, aunque con más latencia. Y operar un servidor TURN propio tiene un costo de ancho de banda real: cada byte que pasa por el relay lo paga quien opera la infraestructura, así que en redes con NAT restrictivo WebRTC deja de ser gratis en términos de servidor.
📖 Resumen en Telegram: Ver resumen
Tu próximo paso: cloná dos pestañas del navegador, levantá el servidor de señalización de este artículo con node servidor-senalizacion.js, y abrí chrome://webrtc-internals en una de ellas para ver en vivo qué candidato ICE gana la negociación.
Preguntas frecuentes
¿WebRTC necesita un servidor?
Sí, pero solo para la señalización inicial, es decir para intercambiar SDP y candidatos ICE. Una vez que la conexión P2P se establece, el servidor de señalización puede desconectarse sin cortar la llamada. Solo si la red obliga a usar TURN, un servidor sigue involucrado durante toda la sesión, retransmitiendo el tráfico.
¿Es segura la comunicación por WebRTC?
Sí: el cifrado con DTLS-SRTP es obligatorio en la especificación, no existe un modo sin cifrar, ni siquiera en conexiones de prueba dentro de una misma red local.
¿Qué diferencia hay entre STUN y TURN?
STUN solo ayuda a descubrir la IP pública y el puerto que ve el mundo exterior detrás de un NAT; no retransmite tráfico. TURN sí retransmite todo el audio, video o datos cuando ninguna ruta directa entre los dos navegadores funciona.
¿Funciona WebRTC en todos los navegadores?
Chrome, Firefox, Safari y Edge lo soportan de forma nativa desde hace años. En apps móviles nativas, no en un navegador embebido, se usa la librería libwebrtc, el mismo motor que usa Chrome, con bindings para iOS y Android.
¿Puedo usar WebRTC solo para transferir archivos, sin video?
Sí: RTCDataChannel es un canal de datos genérico sobre SCTP que no requiere ni cámara ni micrófono. Se puede abrir un RTCPeerConnection exclusivamente para intercambiar datos arbitrarios.
¿Por qué mi videollamada de prueba no conecta entre dos redes distintas?
El motivo más común es un NAT simétrico en alguna de las dos redes, que bloquea los candidatos server-reflexive. La solución es configurar un servidor TURN como respaldo: sin TURN, esas conexiones fallan sin ningún error explícito, solo el estado ICE nunca llega a connected.
Referencias
- Especificación oficial de WebRTC del W3C: define la API completa de RTCPeerConnection, RTCDataChannel y getUserMedia.
- RFC 8445, Interactive Connectivity Establishment (ICE): el protocolo que negocia la ruta de red entre dos extremos.
- RFC 8827, WebRTC Security Architecture: por qué el cifrado DTLS-SRTP es obligatorio en toda sesión.
- MDN Web Docs: WebRTC API: referencia práctica y ejemplos de cada interfaz de la API.
- coturn en GitHub: implementación open source de un servidor STUN/TURN para producción.
📱 ¿Te gusta este contenido? Únete a nuestro canal de Telegram @programacion donde publicamos a diario lo más relevante de tecnología, IA y desarrollo. Resúmenes rápidos, contenido fresco todos los días.
Imagen destacada: Foto de Shubham Dhage en Unsplash
0 Comentarios