Comienza a usar las conexiones entre pares

Las conexiones de pares son la parte de las especificaciones de WebRTC que se ocupa de conectar dos aplicaciones en diferentes computadoras para comunicarse a través de un protocolo de igual a igual. La comunicación entre pares puede ser de video, audio o datos binarios arbitrarios (para los clientes que admiten la API de RTCDataChannel). Para descubrir cómo se pueden conectar dos pares, ambos clientes deben proporcionar una configuración del servidor ICE. Este es un servidor STUN o TURN, y su función es proporcionar candidatos de ICE a cada cliente, que luego se transfieren al par remoto. Esta transferencia de candidatos a ICE se conoce comúnmente como señalización.

Señalización

La especificación de WebRTC incluye APIs para comunicarse con un servidor ICE (Internet Connectivity Establishment), pero el componente de señalización no forma parte de ella. La señalización es necesaria para que dos peers compartan cómo deben conectarse. Por lo general, esto se resuelve a través de una API web normal basada en HTTP (es decir, un servicio REST o algún otro mecanismo de RPC) en la que las aplicaciones web pueden retransmitir la información necesaria antes de que se inicie la conexión de pares.

En el siguiente fragmento de código, se muestra cómo se puede usar este servicio de señalización ficticio para enviar y recibir mensajes de forma asíncrona. Se usará en los ejemplos restantes de esta guía cuando sea necesario.

// Set up an asynchronous communication channel that will be
// used during the peer connection setup
const signalingChannel = new SignalingChannel(remoteClientId);
signalingChannel.addEventListener('message', message => {
    // New message from remote client received
});

// Send an asynchronous message to the remote client
signalingChannel.send('Hello!');

La señalización se puede implementar de muchas maneras diferentes, y la especificación de WebRTC no prefiere ninguna solución específica.

Cómo iniciar conexiones de pares

Cada conexión de pares se controla con un objeto RTCPeerConnection. El constructor de esta clase toma un solo objeto RTCConfiguration como parámetro. Este objeto define cómo se configura la conexión de pares y debe contener información sobre los servidores ICE que se usarán.

Una vez que se crea el objeto RTCPeerConnection, debemos crear una oferta o respuesta de SDP, según si somos el par que llama o el par que recibe. Una vez que se crea la oferta o la respuesta del SDP, se debe enviar al par remoto a través de un canal diferente. El paso de objetos SDP a pares remotos se denomina señalización y no está cubierto por la especificación de WebRTC.

Para iniciar la configuración de la conexión de pares desde el lado de la llamada, creamos un objeto RTCPeerConnection y, luego, llamamos a createOffer() para crear un objeto RTCSessionDescription. La descripción de la sesión se establece como la descripción local con setLocalDescription() y, luego, se envía a través de nuestro canal de señalización al extremo receptor. También configuramos un objeto de escucha en nuestro canal de señalización para cuando se recibe una respuesta a la descripción de la sesión que ofrecimos del lado receptor.

async function makeCall() {
    const configuration = {'iceServers': [{'urls': 'stun:stun.l.google.com:19302'}]}
    const peerConnection = new RTCPeerConnection(configuration);
    signalingChannel.addEventListener('message', async message => {
        if (message.answer) {
            const remoteDesc = new RTCSessionDescription(message.answer);
            await peerConnection.setRemoteDescription(remoteDesc);
        }
    });
    const offer = await peerConnection.createOffer();
    await peerConnection.setLocalDescription(offer);
    signalingChannel.send({'offer': offer});
}

En el lado receptor, esperamos una oferta entrante antes de crear nuestra instancia de RTCPeerConnection. Una vez que eso se hace, configuramos la oferta recibida con setRemoteDescription(). A continuación, llamamos a createAnswer() para crear una respuesta a la oferta recibida. Esta respuesta se establece como la descripción local con setLocalDescription() y, luego, se envía al lado de la llamada a través de nuestro servidor de señalización.

const peerConnection = new RTCPeerConnection(configuration);
signalingChannel.addEventListener('message', async message => {
    if (message.offer) {
        peerConnection.setRemoteDescription(new RTCSessionDescription(message.offer));
        const answer = await peerConnection.createAnswer();
        await peerConnection.setLocalDescription(answer);
        signalingChannel.send({'answer': answer});
    }
});

Una vez que los dos peers establecieron las descripciones de sesión local y remota, conocen las capacidades del peer remoto. Esto no significa que la conexión entre los pares esté lista. Para que esto funcione, debemos recopilar los candidatos a ICE en cada par y transferirlos (a través del canal de señalización) al otro par.

Candidatos de ICE

Antes de que dos pares puedan comunicarse a través de WebRTC, deben intercambiar información de conectividad. Dado que las condiciones de la red pueden variar según varios factores, generalmente se usa un servicio externo para descubrir los posibles candidatos para conectarse a un par. Este servicio se llama ICE y usa un servidor STUN o TURN. STUN significa Session Traversal Utilities for NAT y, por lo general, se usa de forma indirecta en la mayoría de las aplicaciones de WebRTC.

TURN (Traversal Using Relay NAT) es la solución más avanzada que incorpora los protocolos STUN, y la mayoría de los servicios comerciales basados en WebRTC usan un servidor TURN para establecer conexiones entre pares. La API de WebRTC admite STUN y TURN directamente, y se agrupa bajo el término más completo de Establecimiento de conectividad a Internet. Cuando creamos una conexión WebRTC, solemos proporcionar uno o varios servidores ICE en la configuración del objeto RTCPeerConnection.

ICE de goteo

Una vez que se crea un objeto RTCPeerConnection, el framework subyacente usa los servidores ICE proporcionados para recopilar candidatos para el establecimiento de la conectividad (candidatos de ICE). El evento icegatheringstatechange en RTCPeerConnection indica en qué estado se encuentra la recopilación de ICE (new, gathering o complete).

Si bien es posible que un par espere hasta que se complete la recopilación de ICE, suele ser mucho más eficiente usar una técnica de "ICE por goteo" y transmitir cada candidato de ICE al par remoto a medida que se descubre. Esto reducirá significativamente el tiempo de configuración de la conectividad entre pares y permitirá que se inicie una videollamada con menos demoras.

Para recopilar candidatos a ICE, simplemente agrega un objeto de escucha para el evento icecandidate. El evento RTCPeerConnectionIceEvent emitido en ese objeto de escucha contendrá la propiedad candidate que representa un nuevo candidato que se debe enviar al par remoto (consulta Señalización).

// Listen for local ICE candidates on the local RTCPeerConnection
peerConnection.addEventListener('icecandidate', event => {
    if (event.candidate) {
        signalingChannel.send({'new-ice-candidate': event.candidate});
    }
});

// Listen for remote ICE candidates and add them to the local RTCPeerConnection
signalingChannel.addEventListener('message', async message => {
    if (message.iceCandidate) {
        try {
            await peerConnection.addIceCandidate(message.iceCandidate);
        } catch (e) {
            console.error('Error adding received ice candidate', e);
        }
    }
});

Se estableció la conexión

Una vez que se reciban candidatos de ICE, deberíamos esperar que el estado de nuestra conexión de pares cambie a un estado conectado. Para detectar esto, agregamos un objeto de escucha a nuestro RTCPeerConnection en el que escuchamos los eventos connectionstatechange.

// Listen for connectionstatechange on the local RTCPeerConnection
peerConnection.addEventListener('connectionstatechange', event => {
    if (peerConnection.connectionState === 'connected') {
        // Peers connected!
    }
});

Documentación de la API de RTCPeerConnection