Comienza a usar las conexiones entre pares

Las conexiones de par son la parte de las especificaciones de WebRTC que se encarga de conectar dos aplicaciones en diferentes computadoras para comunicarse mediante un protocolo entre pares. La comunicación entre pares puede ser de datos binarios de video, de audio o arbitrarios (para clientes que admiten la API de RTCDataChannel). Para descubrir cómo se pueden conectar dos pares, ambos clientes deben proporcionar una configuración de servidor ICE. Esta puede ser una STUN o un servidor turn, y su función es brindar candidatos ICE a cada cliente para luego transferirlos al par remoto. Por lo general, esta transferencia de candidatos del ICE se denomina señalización.

Señalización

La especificación de WebRTC incluye API para comunicarse con un servidor de ICE (conexión de Internet), pero el componente de señalización no forma parte de él. La señalización es necesaria para que dos pares compartan la forma en que deben conectarse. Por lo general, esto se resuelve mediante una API web normal basada en HTTP (es decir, un servicio de REST o cualquier otro mecanismo de RPC) en la que las aplicaciones web puedan retransmitir la información necesaria antes de que se inicie la conexión de intercambio de tráfico.

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 formas diferentes, y la especificación WebRTC no prefiere ninguna solución específica.

Inicia conexiones de intercambio de tráfico

Cada conexión de intercambio de tráfico está controlada por un objeto RTCPeerConnection. El constructor de esta clase toma un solo objeto RTCConfiguration como su parámetro. Este objeto define cómo se configura la conexión del par y debe contener información sobre los servidores ICE que se usarán.

Una vez que se crea el RTCPeerConnection, debemos crear una oferta o una respuesta de SDP, dependiendo de si somos el intercambio de tráfico o estamos recibiendo el intercambio de tráfico. Una vez que se crea la oferta o 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 la especificación WebRTC no lo permite.

Para iniciar la configuración de conexión de pares desde el lado que realiza la llamada, creamos un objeto RTCPeerConnection y, luego, llamamos a createOffer() para crear un objeto RTCSessionDescription. Esta descripción de la sesión se configura como la descripción local con setLocalDescription() y, luego, se envía a través del canal de señalización al lado receptor. También configuramos un objeto de escucha en nuestro canal de señalización para cuando se recibe una respuesta de la descripción de nuestra sesión 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 del destinatario, se espera una oferta entrante antes de crear la instancia RTCPeerConnection. Una vez hecho esto, 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 mediante el 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 pares establezcan las descripciones de las sesiones locales y remotas, conocerán las capacidades de estas funciones. Esto no significa que la conexión entre apps similares esté lista. Para que esto funcione, debemos recopilar los candidatos de ICE en cada par y transferirlos (a través del canal de señalización) al otro par.

Candidatos al ICE

Antes de que dos pares puedan comunicarse mediante WebRTC, deben intercambiar información de conectividad. Debido a que las condiciones de la red pueden variar en función de diversos factores, por lo general, se usa un servicio externo a fin de descubrir los posibles candidatos para conectarse a un par. Este servicio se llama ICE y usa un servidor STUN o turn. STUN significa Utilidad de recorrido de sesión para 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 turnos directamente, y se recopila mediante el Establecimiento de la Conexión a Internet más completo. Cuando se crea una conexión WebRTC, por lo general, se proporciona uno o varios servidores ICE en la configuración para el objeto RTCPeerConnection.

Trick ICE

Una vez que se crea un objeto RTCPeerConnection, el framework subyacente usa los servidores ICE proporcionados a fin de recopilar candidatos para el establecimiento de la conectividad (candidatos de ICE). El evento icegatheringstatechange en RTCPeerConnection indica en qué estado se encuentra la reunión del 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 hielo TRICE y transmitir cada candidato ICE al par remoto a medida que se descubre. Esto reducirá considerablemente el tiempo de configuración de la conectividad de intercambio de tráfico y permitirá que las videollamadas comiencen con menos demoras.

A fin de recopilar candidatos de ICE, simplemente agrega un objeto de escucha para el evento icecandidate. El RTCPeerConnectionIceEvent emitido en ese objeto de escucha contendrá una propiedad candidate que representa un nuevo candidato que se debe enviar al par remoto (consulta Signaling).

// 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 reciben los candidatos ICE, el estado de nuestra conexión de par terminará cambiando. Para detectar esto, agregamos un objeto de escucha a RTCPeerConnection, en el que escuchamos 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