Las conexiones entre pares es la parte de las especificaciones de WebRTC que se ocupa de conectar dos aplicaciones en diferentes computadoras para comunicarse mediante un protocolo de igual a igual. La comunicación entre pares puede ser de vídeo, datos binarios de audio o arbitraria (para los clientes que apoyan la RTCDataChannel
API). Para descubrir cómo se pueden conectar dos pares, ambos clientes deben proporcionar una configuración de servidor ICE. Este es un servidor STUN o TURN, y su función es proporcionar candidatos ICE a cada cliente que luego se transfieren al par remoto. Esta transferencia de candidatos ICE se denomina comúnmente señalización.
Señalización
La especificación WebRTC incluye API para comunicarse con un servidor ICE (establecimiento de conectividad a Internet), pero el componente de señalización no forma parte de él. La señalización es necesaria para que dos pares compartan cómo deben conectarse. Por lo general, esto se resuelve a través de una API web basada en HTTP (es decir, un servicio REST u otro mecanismo RPC) donde las aplicaciones web pueden transmitir la información necesaria antes de que se inicie la conexión entre pares.
El siguiente fragmento de código muestra cómo este servicio de señalización ficticio se puede utilizar para enviar y recibir mensajes de forma asincrónica. Esto se utilizará 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.
Iniciar conexiones entre pares
Cada conexión entre pares es manejado por un RTCPeerConnection
objeto. El constructor de esta clase toma un único RTCConfiguration
objeto como su parámetro. Este objeto define cómo se configura la conexión entre pares y debe contener información sobre los servidores ICE que se utilizarán.
Una vez que el RTCPeerConnection
se crea que necesitamos para crear una oferta SDP o respuesta, dependiendo de si estamos llamando el par o la recepción de pares. Una vez que se crea la oferta o respuesta SDP, se debe enviar al par remoto a través de un canal diferente. Pasar objetos SDP a pares remotos se denomina señalización y no está cubierto por la especificación WebRTC.
Para iniciar el establecimiento de la comunicación entre pares desde el lado llamante, creamos un RTCPeerConnection
objeto y luego llamamos createOffer()
para crear un RTCSessionDescription
objeto. Esta descripción de la sesión se establece como la descripción local utilizando setLocalDescription()
y después se envía a través de nuestro canal de señalización al lado de recepción. También configuramos un oyente para nuestro canal de señalización para cuando se reciba una respuesta a la descripción de nuestra sesión ofrecida desde el 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 a que una oferta entrante antes de que creamos nuestra RTCPeerConnection
ejemplo. Una vez hecho esto nos propusimos la oferta recibida utilizando setRemoteDescription()
. A continuación, se llama createAnswer()
para crear una respuesta a la oferta recibida. Esta respuesta está configurado como la descripción local utilizando setLocalDescription()
y luego se envían a un lado llamar 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 pares han configurado las descripciones de sesión local y remota, conocen las capacidades del par remoto. Esto no significa que la conexión entre los pares esté lista. Para que esto funcione, necesitamos recopilar los candidatos 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 usando WebRTC, necesitan intercambiar información de conectividad. Dado que las condiciones de la red pueden variar según una serie de factores, generalmente se utiliza un servicio externo para descubrir los posibles candidatos para conectarse a un par. Este servicio se llama ICE y utiliza un servidor STUN o TURN. STUN son las siglas de Session Traversal Utilities para NAT, y generalmente se usa indirectamente en la mayoría de las aplicaciones 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 utilizan un servidor TURN para establecer conexiones entre pares. La API de WebRTC admite STUN y TURN directamente, y se recopila bajo el término más completo Establecimiento de conectividad a Internet. Al crear una conexión WebRTC, por lo general proporcionar una o varios servidores de ICE en la configuración para el RTCPeerConnection
objeto.
Goteo de hielo
Una vez que un RTCPeerConnection
se crea objeto, el marco subyacente utiliza los servidores ICE previstos para reunir los candidatos para el establecimiento de conectividad (ICE) candidatos. El evento icegatheringstatechange
en RTCPeerConnection
señales en qué estado se encuentra la reunión ICE es ( new
, gathering
o complete
).
Si bien es posible que un par espere hasta que se complete la recopilación de ICE, generalmente es mucho más eficiente usar una técnica de "goteo de hielo" y transmitir cada candidato de ICE al par remoto a medida que se descubre. Esto reducirá significativamente el tiempo de configuración para la conectividad entre pares y permitirá que una videollamada comience con menos demoras.
Para reunir los candidatos ICE, basta con añadir un detector para el icecandidate
evento. El RTCPeerConnectionIceEvent
emite en ese oyente contendrá candidate
propiedad que representa un nuevo candidato que debe ser enviado a la distancia entre pares (Ver 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);
}
}
});
Conexión establecida
Una vez que se reciben los candidatos de ICE, debemos esperar que el estado de nuestra conexión entre pares cambie eventualmente a un estado conectado. Para detectar esto, añadimos un oyente de nuestro RTCPeerConnection
donde se escucha para connectionstatechange
eventos.
// Listen for connectionstatechange on the local RTCPeerConnection
peerConnection.addEventListener('connectionstatechange', event => {
if (peerConnection.connectionState === 'connected') {
// Peers connected!
}
});