WebRTC - Guía rápida

La Web ya no es ajena a la comunicación en tiempo real como WebRTC (Web Real-Time Communication)entra en juego. Aunque fue lanzado en mayo de 2011, aún se está desarrollando y sus estándares están cambiando. Un conjunto de protocolos está estandarizado por la comunicación en tiempo real en el grupo de trabajo de navegadores WEB enhttp://tools.ietf.org/wg/rtcweb/ del IETF (Internet Engineering Task Force)mientras que los nuevos conjuntos de API están estandarizados por el Grupo de trabajo de comunicaciones en tiempo real web enhttp://www.w3.org/2011/04/webrtc/ del W3C (World Wide Web Consortium). Con la aparición de WebRTC, las aplicaciones web modernas pueden transmitir fácilmente contenido de audio y video a millones de personas.

Esquema básico

WebRTC le permite configurar conexiones de igual a igual a otros navegadores web de forma rápida y sencilla. Para construir una aplicación de este tipo desde cero, necesitaría una gran cantidad de marcos y bibliotecas que se ocupen de problemas típicos como la pérdida de datos, la caída de la conexión y el cruce de NAT. Con WebRTC, todo esto viene integrado en el navegador de fábrica. Esta tecnología no necesita complementos ni software de terceros. Es de código abierto y su código fuente está disponible gratuitamente enhttp://www.webrtc.org/.

La API de WebRTC incluye captura de medios, codificación y decodificación de audio y video, capa de transporte y administración de sesiones.

Captura de medios

El primer paso es acceder a la cámara y al micrófono del dispositivo del usuario. Detectamos el tipo de dispositivos disponibles, obtenemos el permiso del usuario para acceder a estos dispositivos y administrar la transmisión.

Codificación y decodificación de audio y video

No es una tarea fácil enviar un flujo de datos de audio y video a través de Internet. Aquí es donde se utilizan la codificación y la decodificación. Este es el proceso de dividir cuadros de video y ondas de audio en trozos más pequeños y comprimirlos. Este algoritmo se llamacodec. Existe una enorme cantidad de códecs diferentes, que son mantenidos por diferentes empresas con diferentes objetivos comerciales. También hay muchos códecs dentro de WebRTC como H.264, iSAC, Opus y VP8. Cuando dos navegadores se conectan entre sí, eligen el códec compatible más óptimo entre dos usuarios. Afortunadamente, WebRTC realiza la mayor parte de la codificación entre bastidores.

Capa de transporte

La capa de transporte gestiona el orden de los paquetes, se ocupa de la pérdida de paquetes y se conecta a otros usuarios. Una vez más, la API de WebRTC nos brinda un fácil acceso a los eventos que nos dicen cuándo hay problemas con la conexión.

Gestión de sesiones

La gestión de sesiones se ocupa de gestionar, abrir y organizar conexiones. Esto se llama comúnmentesignaling. Si transfiere secuencias de audio y video al usuario, también tiene sentido transferir datos colaterales. Esto lo hace elRTCDataChannel API.

Los ingenieros de empresas como Google, Mozilla, Opera y otras han hecho un gran trabajo para llevar esta experiencia en tiempo real a la Web.

Compatibilidad del navegador

Los estándares WebRTC son uno de los que evolucionan más rápidamente en la web, por lo que no significa que todos los navegadores admitan las mismas funciones al mismo tiempo. Para comprobar si su navegador es compatible con WebRTC o no, puede visitarhttp://caniuse.com/#feat=rtcpeerconnection. A lo largo de todos los tutoriales, te recomiendo que uses Chrome para todos los ejemplos.

Probar WebRTC

Comencemos a usar WebRTC ahora mismo. Navegue con su navegador al sitio de demostración enhttps://apprtc.appspot.com/

Haga clic en el botón "UNIRSE". Debería ver una notificación desplegable.

Haga clic en el botón "Permitir" para comenzar a transmitir su video y audio a la página web. Debería ver una transmisión de video de usted mismo.

Ahora abra la URL en la que se encuentra actualmente en una nueva pestaña del navegador y haga clic en "UNIRSE". Debería ver dos transmisiones de video: una de su primer cliente y otra del segundo.

Ahora debe comprender por qué WebRTC es una herramienta poderosa.

Casos de uso

La web en tiempo real abre la puerta a una gama completamente nueva de aplicaciones, que incluyen chat basado en texto, uso compartido de pantalla y archivos, juegos, video chat y más. Además de la comunicación, puede utilizar WebRTC para otros fines como:

  • marketing en tiempo real
  • publicidad en tiempo real
  • comunicaciones de back office (CRM, ERP, SCM, FFM)
  • Gestión de recursos humanos
  • redes sociales
  • servicios de citas
  • consultas médicas online
  • servicios financieros
  • surveillance
  • juegos multijugador
  • transmisión en vivo
  • e-learning

Resumen

Ahora debería tener una comprensión clara del término WebRTC. También debe tener una idea de qué tipos de aplicaciones se pueden construir con WebRTC, ya que ya lo ha probado en su navegador. En resumen, WebRTC es una tecnología bastante útil.

La arquitectura general de WebRTC tiene un gran nivel de complejidad.

Aquí puede encontrar tres capas diferentes:

  • API for web developers - esta capa contiene todas las API necesarias para el desarrollador web, incluidos los objetos RTCPeerConnection, RTCDataChannel y MediaStrean.

  • API para fabricantes de navegadores

  • API reemplazable, que los fabricantes de navegadores pueden conectar.

Los componentes de transporte permiten establecer conexiones a través de varios tipos de redes, mientras que los motores de voz y video son marcos responsables de transferir transmisiones de audio y video desde una tarjeta de sonido y una cámara a la red. Para los desarrolladores web, la parte más importante es la API de WebRTC.

Si miramos la arquitectura WebRTC desde el lado cliente-servidor, podemos ver que uno de los modelos más utilizados está inspirado en el Trapezoide SIP (Protocolo de inicio de sesión).

En este modelo, ambos dispositivos ejecutan una aplicación web desde diferentes servidores. El objeto RTCPeerConnection configura transmisiones para que puedan conectarse entre sí, de igual a igual. Esta señalización se realiza mediante HTTP o WebSockets.

Pero el modelo más utilizado es Triangle -

En este modelo, ambos dispositivos utilizan la misma aplicación web. Ofrece al desarrollador web más flexibilidad a la hora de gestionar las conexiones de los usuarios.

La API de WebRTC

Consiste en algunos objetos javascript principales:

  • RTCPeerConnection
  • MediaStream
  • RTCDataChannel

El objeto RTCPeerConnection

Este objeto es el principal punto de entrada a la API de WebRTC. Nos ayuda a conectarnos con pares, inicializar conexiones y adjuntar transmisiones multimedia. También gestiona una conexión UDP con otro usuario.

La tarea principal del objeto RTCPeerConnection es configurar y crear una conexión de igual. Podemos enganchar fácilmente puntos clave de la conexión porque este objeto dispara un conjunto de eventos cuando aparecen. Estos eventos le dan acceso a la configuración de nuestra conexión -

RTCPeerConnection es un objeto javascript simple, que puede crear simplemente de esta manera:

[code] 
var conn = new RTCPeerConnection(conf); 

conn.onaddstream = function(stream) { 
   // use stream here 
}; 

[/code]

El objeto RTCPeerConnection acepta un parámetro conf , que cubriremos más adelante en estos tutoriales. El evento onaddstream se activa cuando el usuario remoto agrega una transmisión de video o audio a su conexión de pares.

API MediaStream

Los navegadores modernos dan a un desarrollador acceso a la API getUserMedia , también conocida como API MediaStream . Hay tres puntos clave de funcionalidad:

  • Le da a un desarrollador acceso a un objeto de transmisión que representa transmisiones de video y audio.

  • Gestiona la selección de los dispositivos de entrada del usuario en caso de que un usuario tenga varias cámaras o micrófonos en su dispositivo.

  • Proporciona un nivel de seguridad que pregunta al usuario todo el tiempo que quiera recuperar la transmisión.

Para probar esta API, creemos una página HTML simple. Mostrará un solo elemento <video>, le pedirá permiso al usuario para usar la cámara y mostrará una transmisión en vivo desde la cámara en la página. Cree un archivo index.html y agregue -

[code] 
<html>
 
   <head> 
      <meta charset = "utf-8"> 
   </head>
	
   <body> 
      <video autoplay></video> 
      <script src = "client.js"></script> 
   </body> 
	 
</html> 
[/code]

Luego agregue un archivo client.js -

[code] 
//checks if the browser supports WebRTC 

function hasUserMedia() { 
   navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia 
      || navigator.mozGetUserMedia || navigator.msGetUserMedia; 
   return !!navigator.getUserMedia; 
}
 
if (hasUserMedia()) { 
   navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia
      || navigator.mozGetUserMedia || navigator.msGetUserMedia;
		
   //get both video and audio streams from user's camera 
   navigator.getUserMedia({ video: true, audio: true }, function (stream) { 
      var video = document.querySelector('video'); 
		
      //insert stream into the video tag 
      video.src = window.URL.createObjectURL(stream); 
   }, function (err) {}); 
	
}else {
   alert("Error. WebRTC is not supported!"); 
}
[/code]

Ahora abra index.html y debería ver la secuencia de video que muestra su cara.

Pero tenga cuidado, porque WebRTC solo funciona en el lado del servidor. Si simplemente abre esta página con el navegador, no funcionará. Debe alojar estos archivos en los servidores Apache o Node, o en el que prefiera.

El objeto RTCDataChannel

Además de enviar transmisiones de medios entre pares, también puede enviar datos adicionales mediante la API de DataChannel . Esta API es tan simple como MediaStream API. El trabajo principal es crear un canal procedente de un objeto RTCPeerConnection existente -

[code] 
var peerConn = new RTCPeerConnection(); 

//establishing peer connection 
//... 
//end of establishing peer connection 
var dataChannel = peerConnection.createDataChannel("myChannel", dataChannelOptions); 

// here we can start sending direct messages to another peer 
[/code]

Esto es todo lo que necesita, solo dos líneas de código. Todo lo demás se realiza en la capa interna del navegador. Puede crear un canal en cualquier conexión del mismo nivel hasta que se cierre el objeto RTCPeerConnection .

Resumen

Ahora debería tener un conocimiento firme de la arquitectura WebRTC. También cubrimos las API de MediaStream, RTCPeerConnection y RTCDataChannel. La API de WebRTC es un objetivo en movimiento, así que manténgase siempre actualizado con las últimas especificaciones.

Antes de comenzar a construir nuestras aplicaciones WebRTC, debemos configurar nuestro entorno de codificación. En primer lugar, debe tener un editor de texto o IDE donde pueda editar HTML y Javascript. Existe la posibilidad de que ya haya elegido el preferido mientras lee este tutorial. En cuanto a mí, estoy usando WebStorm IDE. Puede descargar su versión de prueba enhttps://www.jetbrains.com/webstorm/. También estoy usando Linux Mint como mi sistema operativo preferido.

El otro requisito para las aplicaciones WebRTC comunes es tener un servidor para alojar los archivos HTML y Javascript. El código no funcionará simplemente haciendo doble clic en los archivos porque el navegador no puede conectarse a cámaras y micrófonos a menos que los archivos estén siendo servidos por un servidor real. Esto se hace obviamente debido a problemas de seguridad.

Hay toneladas de servidores web diferentes, pero en este tutorial, usaremos Node.js con node-static -

  • Visitar https://nodejs.org/en/ y descargue la última versión de Node.js.

  • Descomprímalo en el directorio / usr / local / nodejs.

  • Abra el archivo /home/YOUR_USERNAME/.profile y agregue la siguiente línea al final - export PATH = $ PATH: / usr / local / nodejs / bin

  • Puede reiniciar su computadora o ejecutar la fuente /home/YOUR_USERNAME/.profile

  • Ahora el comando de nodo debería estar disponible desde la línea de comandos. El comando npm también está disponible. NMP es el administrador de paquetes de Node.js. Puedes aprender más enhttps://www.npmjs.com/.

  • Abra una terminal y ejecute sudo npm install -g node-static . Esto instalará el servidor web estático para Node.js.

  • Ahora navegue a cualquier directorio que contenga los archivos HTML y ejecute el comando estático dentro del directorio para iniciar su servidor web.

  • Puedes navegar a http://localhost:8080 para ver sus archivos.

Hay otra forma de instalar nodejs. Simplemente ejecute sudo apt-get install nodejs en la ventana de terminal.

Para probar su instalación de Node.js, abra su terminal y ejecute el comando de nodo . Escriba algunos comandos para verificar cómo funciona:

Node.js ejecuta archivos Javascript, así como comandos escritos en la terminal. Cree un archivo index.js con el siguiente contenido:

console.log(“Testing Node.js”);

Luego, ejecute el comando de índice de nodo . Verá lo siguiente:

Al construir nuestro servidor de señalización usaremos una biblioteca de WebSockets para Node.js. Para instalar en run npm instale ws en la terminal.

Para probar nuestro servidor de señalización, usaremos la utilidad wscat. Para instalarlo, ejecute npm install -g wscat en la ventana de su terminal.

S. No Protocolos y descripción
1 Protocolos WebRTC

Las aplicaciones WebRTC utilizan UDP (Protocolo de datagramas de usuario) como protocolo de transporte. La mayoría de las aplicaciones web actuales se crean con el uso de TCP (Protocolo de control de transmisión)

2 Protocolo de descripción de sesión

El SDP es una parte importante de WebRTC. Es un protocolo que pretende describir las sesiones de comunicación con los medios.

3 Encontrar una ruta

Para conectarse con otro usuario, debe encontrar una ruta clara alrededor de su propia red y la red del otro usuario. Pero hay posibilidades de que la red que está utilizando tenga varios niveles de control de acceso para evitar problemas de seguridad.

4 Protocolo de transmisión de control de flujo

Con la conexión entre pares, tenemos la capacidad de enviar rápidamente datos de video y audio. El protocolo SCTP se utiliza hoy en día para enviar datos BLOB además de nuestra conexión de pares configurada actualmente cuando se utiliza el objeto RTCDataChannel.

Resumen

En este capítulo, cubrimos varias de las tecnologías que permiten conexiones entre pares, como UDP, TCP, STUN, TURN, ICE y SCTP. Ahora debería tener una comprensión superficial de cómo funciona SDP y sus casos de uso.

La API de MediaStream fue diseñada para acceder fácilmente a las transmisiones de medios desde cámaras y micrófonos locales. El método getUserMedia () es la forma principal de acceder a los dispositivos de entrada locales.

La API tiene algunos puntos clave:

  • Un flujo de medios en tiempo real está representado por un objeto de flujo en forma de video o audio.

  • Proporciona un nivel de seguridad a través de permisos de usuario que le preguntan al usuario antes de que una aplicación web pueda comenzar a buscar una transmisión.

  • La selección de dispositivos de entrada la maneja la API MediaStream (por ejemplo, cuando hay dos cámaras o micrófonos conectados al dispositivo)

Cada objeto MediaStream incluye varios objetos MediaStreamTrack. Representan video y audio de diferentes dispositivos de entrada.

Cada objeto MediaStreamTrack puede incluir varios canales (canales de audio derecho e izquierdo). Estas son las partes más pequeñas definidas por MediaStream API.

Hay dos formas de generar objetos MediaStream. Primero, podemos convertir la salida en un elemento de video o audio. En segundo lugar, podemos enviar la salida al objeto RTCPeerConnection, que luego la envía a un par remoto.

Usando la API de MediaStream

Creemos una aplicación WebRTC simple. Mostrará un elemento de video en la pantalla, le pedirá permiso al usuario para usar la cámara y mostrará una transmisión de video en vivo en el navegador. Crea un archivo index.html -

<!DOCTYPE html> 
<html lang = "en">
 
   <head> 
      <meta charset = "utf-8" /> 
   </head> 
	
   <body> 
      <video autoplay></video> 
      <script src = "client.js"></script> 
   </body>
	
</html>

Luego cree el archivo client.js y agregue lo siguiente;

function hasUserMedia() { 
   //check if the browser supports the WebRTC 
   return !!(navigator.getUserMedia || navigator.webkitGetUserMedia || 
      navigator.mozGetUserMedia); 
} 

if (hasUserMedia()) { 
   navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia
      || navigator.mozGetUserMedia; 
		
   //enabling video and audio channels 
   navigator.getUserMedia({ video: true, audio: true }, function (stream) { 
      var video = document.querySelector('video'); 
		
      //inserting our stream to the video tag     
      video.src = window.URL.createObjectURL(stream); 
   }, function (err) {}); 
} else { 
   alert("WebRTC is not supported"); 
}

Aquí creamos la función hasUserMedia () que verifica si WebRTC es compatible o no. Luego accedemos a la función getUserMedia donde el segundo parámetro es una devolución de llamada que acepta el flujo proveniente del dispositivo del usuario. Luego cargamos nuestro flujo en el elemento de video usando window.URL.createObjectURL que crea una URL que representa el objeto dado en el parámetro.

Ahora actualice su página, haga clic en Permitir, y debería ver su cara en la pantalla.

Recuerde ejecutar todos sus scripts usando el servidor web. Ya hemos instalado uno en el Tutorial del entorno WebRTC.

API MediaStream

Propiedades

  • MediaStream.active (read only) - Devuelve verdadero si MediaStream está activo o falso en caso contrario.

  • MediaStream.ended (read only, deprecated)- Devuelve verdadero si el evento finalizado se ha disparado en el objeto, lo que significa que la transmisión se ha leído por completo, o falso si no se ha alcanzado el final de la transmisión.

  • MediaStream.id (read only) - Un identificador único para el objeto.

  • MediaStream.label (read only, deprecated) - Un identificador único asignado por el agente de usuario.

Puede ver cómo se ven las propiedades anteriores en mi navegador:

Controladores de eventos

  • MediaStream.onactive- Un controlador para un evento activo que se activa cuando un objeto MediaStream se vuelve activo.

  • MediaStream.onaddtrack- Un controlador para un evento addtrack que se activa cuando se agrega un nuevo objeto MediaStreamTrack .

  • MediaStream.onended (deprecated)- Un controlador para un evento finalizado que se activa cuando finaliza la transmisión.

  • MediaStream.oninactive- Un controlador para un evento inactivo que se activa cuando un objeto MediaStream se vuelve inactivo.

  • MediaStream.onremovetrack: Un controlador para un evento removetrack que se activa cuando se elimina un objeto MediaStreamTrack .

Métodos

  • MediaStream.addTrack()- Agrega el objeto MediaStreamTrack dado como argumento al MediaStream. Si la pista ya se ha agregado, no pasa nada.

  • MediaStream.clone() - Devuelve un clon del objeto MediaStream con una nueva ID.

  • MediaStream.getAudioTracks()- Devuelve una lista de los objetos de audio MediaStreamTrack del objeto MediaStream .

  • MediaStream.getTrackById()- Devuelve la pista por ID. Si el argumento está vacío o no se encuentra el ID, devuelve nulo. Si varias pistas tienen el mismo ID, devuelve la primera.

  • MediaStream.getTracks()- Devuelve una lista de todos MediaStreamTrack objetos de la MediaStream objeto.

  • MediaStream.getVideoTracks()- Devuelve una lista de los objetos de vídeo MediaStreamTrack del objeto MediaStream .

  • MediaStream.removeTrack()- Elimina el objeto MediaStreamTrack proporcionado como argumento de MediaStream. Si la pista ya se ha eliminado, no pasa nada.

Para probar las API anteriores, cambie el index.html de la siguiente manera:

<!DOCTYPE html> 
<html lang = "en">
 
   <head> 
      <meta charset = "utf-8" /> 
   </head>
	
   <body> 
      <video autoplay></video> 
      <div><button id = "btnGetAudioTracks">getAudioTracks()
         </button></div> 
      <div><button id = "btnGetTrackById">getTrackById()
         </button></div> 
      <div><button id = "btnGetTracks">getTracks()</button></div> 
      <div><button id = "btnGetVideoTracks">getVideoTracks()
         </button></div> 
      <div><button id = "btnRemoveAudioTrack">removeTrack() - audio
         </button></div> 
      <div><button id = "btnRemoveVideoTrack">removeTrack() - video
         </button></div> 
      <script src = "client.js"></script> 
   </body> 
	
</html>

Agregamos algunos botones para probar varias API de MediaStream. Entonces deberíamos agregar controladores de eventos para nuestro botón recién creado. Modifique el archivo client.js de esta manera:

var stream;
  
function hasUserMedia() { 
   //check if the browser supports the WebRTC 
   return !!(navigator.getUserMedia || navigator.webkitGetUserMedia || 
      navigator.mozGetUserMedia); 
} 
 
if (hasUserMedia()) {
   navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia
      || navigator.mozGetUserMedia; 
		
   //enabling video and audio channels 
   navigator.getUserMedia({ video: true, audio: true }, function (s) { 
      stream = s; 
      var video = document.querySelector('video'); 
		
      //inserting our stream to the video tag     
      video.src = window.URL.createObjectURL(stream); 
   }, function (err) {}); 
} else { 
   alert("WebRTC is not supported"); 
}
  
btnGetAudioTracks.addEventListener("click", function(){ 
   console.log("getAudioTracks"); 
   console.log(stream.getAudioTracks()); 
});
  
btnGetTrackById.addEventListener("click", function(){ 
   console.log("getTrackById"); 
   console.log(stream.getTrackById(stream.getAudioTracks()[0].id)); 
});
  
btnGetTracks.addEventListener("click", function(){ 
   console.log("getTracks()"); 
   console.log(stream.getTracks()); 
});
 
btnGetVideoTracks.addEventListener("click", function(){ 
   console.log("getVideoTracks()"); 
   console.log(stream.getVideoTracks()); 
});

btnRemoveAudioTrack.addEventListener("click", function(){ 
   console.log("removeAudioTrack()"); 
   stream.removeTrack(stream.getAudioTracks()[0]); 
});
  
btnRemoveVideoTrack.addEventListener("click", function(){ 
   console.log("removeVideoTrack()"); 
   stream.removeTrack(stream.getVideoTracks()[0]); 
});

Ahora actualice su página. Haga clic en el botón getAudioTracks () , luego haga clic en el botón removeTrack () - audio . Ahora debería eliminarse la pista de audio. Luego haz lo mismo con la pista de video.

Si hace clic en el botón getTracks () , debería ver todas las MediaStreamTracks (todas las entradas de audio y video conectadas). Luego haga clic en getTrackById () para obtener el audio MediaStreamTrack.

Resumen

En este capítulo, creamos una aplicación WebRTC simple usando la API MediaStream. Ahora debería tener una descripción general clara de las diversas API de MediaStream que hacen que WebRTC funcione.

La API RTCPeerConnection es el núcleo de la conexión peer-to-peer entre cada uno de los navegadores. Para crear los objetos RTCPeerConnection simplemente escriba

var pc = RTCPeerConnection(config);

donde el argumento de configuración contiene al menos una clave, iceServers. Es una serie de objetos URL que contienen información sobre los servidores STUN y TURN, que se utilizan durante la búsqueda de los candidatos ICE. Puede encontrar una lista de servidores STUN públicos disponibles en code.google.com

Dependiendo de si usted es el llamador o el destinatario de la llamada, el objeto RTCPeerConnection se utiliza de una manera ligeramente diferente en cada lado de la conexión.

Aquí hay un ejemplo del flujo del usuario:

  • Registre el controlador onicecandidate . Envía los candidatos de ICE al otro par, a medida que los recibe.

  • Registre el controlador onaddstream . Maneja la visualización de la transmisión de video una vez que se recibe del par remoto.

  • Registre el controlador de mensajes . Su servidor de señalización también debe tener un controlador para los mensajes recibidos del otro par. Si el mensaje contiene el objeto RTCSessionDescription , debe agregarse al objeto RTCPeerConnection mediante el método setRemoteDescription () . Si el mensaje contiene el objeto RTCIceCandidate , debe agregarse al objeto RTCPeerConnection mediante el método addIceCandidate () .

  • Utilice getUserMedia () para configurar su flujo de medios local y agregarlo al objeto RTCPeerConnection usando el método addStream () .

  • Inicie el proceso de negociación de oferta / respuesta. Este es el único paso donde el flujo de la persona que llama es diferente al de la persona que llama. La persona que llama comienza la negociación utilizando el método createOffer () y registra una devolución de llamada que recibe el objeto RTCSessionDescription . A continuación, esta devolución de llamada debe agregar este RTCSessionDescription objeto a su RTCPeerConnection objeto utilizando setLocalDescription () . Y finalmente, la persona que llama debe enviar esta RTCSessionDescription al par remoto usando el servidor de señalización. El destinatario, por otro lado, registra la misma devolución de llamada, pero en el método createAnswer () . Tenga en cuenta que el flujo de llamadas se inicia solo después de que se recibe la oferta de la persona que llama.

API RTCPeerConnection

Propiedades

  • RTCPeerConnection.iceConnectionState (read only)- Devuelve una enumeración RTCIceConnectionState que describe el estado de la conexión. Se dispara un evento iceconnectionstatechange cuando cambia este valor. Los posibles valores -

    • new - el agente de ICE está esperando candidatos remotos o reuniendo direcciones

    • checking - el agente de ICE tiene candidatos remotos, pero aún no ha encontrado una conexión

    • connected - el agente de ICE ha encontrado una conexión utilizable, pero aún está comprobando más candidatos remotos para una mejor conexión.

    • completed - el agente de ICE encontró una conexión utilizable y dejó de probar candidatos remotos.

    • failed - el agente de ICE ha verificado todos los candidatos remotos pero no encontró una coincidencia para al menos un componente.

    • disconnected - al menos un componente ya no está vivo.

    • closed - el agente de ICE está cerrado.

  • RTCPeerConnection.iceGatheringState (read only) - Devuelve una enumeración RTCIceGatheringState que describe el estado de recopilación de ICE para la conexión -

    • new - el objeto se acaba de crear.

    • gathering - el agente de ICE está en proceso de reunir candidatos

    • complete el agente de ICE ha terminado de reunirse.

  • RTCPeerConnection.localDescription (read only)- Devuelve una RTCSessionDescription que describe la sesión local. Puede ser nulo si aún no se ha establecido.

  • RTCPeerConnection.peerIdentity (read only)- Devuelve una RTCIdentityAssertion. Consiste en un idp (nombre de dominio) y un nombre que representa la identidad del par remoto.

  • RTCPeerConnection.remoteDescription (read only)- Devuelve una RTCSessionDescription que describe la sesión remota. Puede ser nulo si aún no se ha establecido.

  • RTCPeerConnection.signalingState (read only)- Devuelve una enumeración RTCSignalingState que describe el estado de señalización de la conexión local. Este estado describe la oferta de SDP. Se dispara un evento de señalización de cambio de estado cuando este valor cambia. Los posibles valores -

    • stable- El estado inicial. No hay ningún intercambio de oferta / respuesta SDP en curso.

    • have-local-offer - el lado local de la conexión ha aplicado localmente una oferta SDP.

    • have-remote-offer - el lado remoto de la conexión ha aplicado localmente una oferta SDP.

    • have-local-pranswer - Se ha aplicado una oferta de SDP remota y se ha aplicado localmente una respuesta de SDP.

    • have-remote-pranswer - Se ha aplicado un SDP local y una respuesta de SDP se ha aplicado de forma remota.

    • closed - la conexión está cerrada.

Controladores de eventos

S.No. Controladores de eventos y descripción
1

RTCPeerConnection.onaddstream

Este controlador se llama cuando se activa el evento addstream. Este evento se envía cuando el par remoto agrega un MediaStream a esta conexión.

2

RTCPeerConnection.ondatachannel

Este controlador se llama cuando se activa el evento de canal de datos. Este evento se envía cuando se agrega un RTCDataChannel a esta conexión.

3

RTCPeerConnection.onicecandidate

Este controlador se llama cuando se dispara el evento icecandidate. Este evento se envía cuando se agrega un objeto RTCIceCandidate al script.

4

RTCPeerConnection.oniceconnectionstatechange

Este controlador se llama cuando se activa el evento iceconnectionstatechange. Este evento se envía cuando cambia el valor de iceConnectionState.

5

RTCPeerConnection.onidentityresult

Se llama a este controlador cuando se activa el evento identityresult. Este evento se envía cuando se genera una afirmación de identidad durante la creación de una oferta o una respuesta a través de getIdentityAssertion ().

6

RTCPeerConnection.onidpassertionerror

Este controlador se llama cuando se activa el evento idpassertionerror. Este evento se envía cuando el IdP (proveedor de identidad) encuentra un error al generar una afirmación de identidad.

7

RTCPeerConnection.onidpvalidation

Este controlador se llama cuando se activa el evento idpvalidationerror. Este evento se envía cuando el IdP (proveedor de identidad) encuentra un error al validar una afirmación de identidad.

8

RTCPeerConnection.onnegotiationneeded

Este controlador se llama cuando se activa el evento de negociación necesaria. Este evento es enviado por el navegador para informar que la negociación será necesaria en algún momento en el futuro.

9

RTCPeerConnection.onpeeridentity

Este controlador se llama cuando se activa el evento de identidad de pares. Este evento se envía cuando se ha establecido y verificado una identidad de par en esta conexión.

10

RTCPeerConnection.onremovestream

Se llama a este controlador cuando se activa el evento signalingstatechange. Este evento se envía cuando cambia el valor de signalingState.

11

RTCPeerConnection.onsignalingstatechange

Este controlador se llama cuando se activa el evento removestream. Este evento se envía cuando se elimina un MediaStream de esta conexión.

Métodos

S.No. Métodos y descripción
1

RTCPeerConnection()

Devuelve un nuevo objeto RTCPeerConnection.

2

RTCPeerConnection.createOffer()

Crea una oferta (solicitud) para encontrar un par remoto. Los dos primeros parámetros de este método son las devoluciones de llamada de éxito y error. El tercer parámetro opcional son opciones, como habilitar transmisiones de audio o video.

3

RTCPeerConnection.createAnswer()

Crea una respuesta a la oferta recibida por el par remoto durante el proceso de negociación de oferta / respuesta. Los dos primeros parámetros de este método son las devoluciones de llamada de éxito y error. El tercer parámetro opcional son opciones para que se cree la respuesta.

4

RTCPeerConnection.setLocalDescription()

Cambia la descripción de la conexión local. La descripción define las propiedades de la conexión. La conexión debe ser compatible con descripciones nuevas y antiguas. El método toma tres parámetros, objeto RTCSessionDescription, devolución de llamada si el cambio de descripción tiene éxito, devolución de llamada si falla el cambio de descripción.

5

RTCPeerConnection.setRemoteDescription()

Cambia la descripción de la conexión remota. La descripción define las propiedades de la conexión. La conexión debe ser compatible con descripciones nuevas y antiguas. El método toma tres parámetros, objeto RTCSessionDescription, devolución de llamada si el cambio de descripción tiene éxito, devolución de llamada si falla el cambio de descripción.

6

RTCPeerConnection.updateIce()

Actualiza el proceso del agente ICE de hacer ping a candidatos remotos y reunir candidatos locales.

7

RTCPeerConnection.addIceCandidate()

Proporciona un candidato remoto al agente de ICE.

8

RTCPeerConnection.getConfiguration()

Devuelve un objeto RTCConfiguration. Representa la configuración del objeto RTCPeerConnection.

9

RTCPeerConnection.getLocalStreams()

Devuelve una matriz de conexión MediaStream local.

10

RTCPeerConnection.getRemoteStreams()

Devuelve una matriz de conexión MediaStream remota.

11

RTCPeerConnection.getStreamById()

Devuelve MediaStream local o remoto por el ID proporcionado.

12

RTCPeerConnection.addStream()

Agrega un MediaStream como fuente local de video o audio.

13

RTCPeerConnection.removeStream()

Elimina un MediaStream como fuente local de video o audio.

14

RTCPeerConnection.close()

Cierra una conexión.

15

RTCPeerConnection.createDataChannel()

Crea un nuevo RTCDataChannel.

dieciséis

RTCPeerConnection.createDTMFSender()

Crea un nuevo RTCDTMFSender, asociado a un MediaStreamTrack específico. Permite enviar señalización telefónica DTMF (multifrecuencia de dos tonos) a través de la conexión.

17

RTCPeerConnection.getStats()

Crea un nuevo RTCStatsReport que contiene estadísticas sobre la conexión.

18

RTCPeerConnection.setIdentityProvider()

Establece el IdP. Toma tres parámetros: el nombre, el protocolo utilizado para comunicarse y un nombre de usuario opcional.

19

RTCPeerConnection.getIdentityAssertion()

Reúne una afirmación de identidad. No se espera tratar este método en la aplicación. Por lo tanto, puede llamarlo explícitamente solo para anticipar la necesidad.

Establecer una conexión

Ahora creemos una aplicación de ejemplo. En primer lugar, ejecute el servidor de señalización que creamos en el tutorial "servidor de señalización" a través de "servidor de nodo".

Habrá dos entradas de texto en la página, una para un inicio de sesión y otra para un nombre de usuario al que queremos conectarnos. Cree un archivo index.html y agregue el siguiente código:

<html lang = "en"> 
   <head> 
      <meta charset = "utf-8" /> 
   </head>
	
   <body> 
	
      <div> 
         <input type = "text" id = "loginInput" /> 
         <button id = "loginBtn">Login</button> 
      </div> 
	
      <div> 
         <input type = "text" id = "otherUsernameInput" />
         <button id = "connectToOtherUsernameBtn">Establish connection</button> 
      </div> 
		
      <script src = "client2.js"></script>
		
   </body>
	
</html>

Puede ver que hemos agregado la entrada de texto para un inicio de sesión, el botón de inicio de sesión, la entrada de texto para el nombre de usuario del otro compañero y el botón de conectarse con él. Ahora cree un archivo client.js y agregue el siguiente código:

var connection = new WebSocket('ws://localhost:9090'); 
var name = ""; 
 
var loginInput = document.querySelector('#loginInput'); 
var loginBtn = document.querySelector('#loginBtn'); 
var otherUsernameInput = document.querySelector('#otherUsernameInput'); 
var connectToOtherUsernameBtn = document.querySelector('#connectToOtherUsernameBtn'); 
var connectedUser, myConnection;
  
//when a user clicks the login button 
loginBtn.addEventListener("click", function(event){ 
   name = loginInput.value; 
	
   if(name.length > 0){ 
      send({ 
         type: "login", 
         name: name 
      }); 
   } 
	
});
  
//handle messages from the server 
connection.onmessage = function (message) { 
   console.log("Got message", message.data);
   var data = JSON.parse(message.data); 
	
   switch(data.type) { 
      case "login": 
         onLogin(data.success); 
         break; 
      case "offer": 
         onOffer(data.offer, data.name); 
         break; 
      case "answer": 
         onAnswer(data.answer); 
         break; 
      case "candidate": 
         onCandidate(data.candidate); 
         break; 
      default: 
         break; 
   } 
};
  
//when a user logs in 
function onLogin(success) { 

   if (success === false) { 
      alert("oops...try a different username"); 
   } else { 
      //creating our RTCPeerConnection object 
		
      var configuration = { 
         "iceServers": [{ "url": "stun:stun.1.google.com:19302" }] 
      }; 
		
      myConnection = new webkitRTCPeerConnection(configuration); 
      console.log("RTCPeerConnection object was created"); 
      console.log(myConnection); 
  
      //setup ice handling
      //when the browser finds an ice candidate we send it to another peer 
      myConnection.onicecandidate = function (event) { 
		
         if (event.candidate) { 
            send({ 
               type: "candidate", 
               candidate: event.candidate 
            }); 
         } 
      }; 
   } 
};
  
connection.onopen = function () { 
   console.log("Connected"); 
};
  
connection.onerror = function (err) { 
   console.log("Got error", err); 
};
  
// Alias for sending messages in JSON format 
function send(message) { 

   if (connectedUser) { 
      message.name = connectedUser; 
   } 
	
   connection.send(JSON.stringify(message)); 
};

Puede ver que establecemos una conexión de socket a nuestro servidor de señalización. Cuando un usuario hace clic en el botón de inicio de sesión, la aplicación envía su nombre de usuario al servidor. Si el inicio de sesión es exitoso, la aplicación crea el objeto RTCPeerConnection y configura un controlador de candidatos de hielo que envía todos los candidatos de hielo encontrados al otro par. Ahora abra la página e intente iniciar sesión. Debería ver la siguiente salida de la consola:

El siguiente paso es crear una oferta para el otro compañero. Agregue el siguiente código a su archivo client.js :

//setup a peer connection with another user 
connectToOtherUsernameBtn.addEventListener("click", function () { 
 
   var otherUsername = otherUsernameInput.value; 
   connectedUser = otherUsername;
	
   if (otherUsername.length > 0) { 
      //make an offer 
      myConnection.createOffer(function (offer) { 
         console.log(); 
         send({ 
            type: "offer", 
            offer: offer 
         });
			
         myConnection.setLocalDescription(offer); 
      }, function (error) { 
         alert("An error has occurred."); 
      }); 
   } 
}); 
 
//when somebody wants to call us 
function onOffer(offer, name) { 
   connectedUser = name; 
   myConnection.setRemoteDescription(new RTCSessionDescription(offer)); 
	
   myConnection.createAnswer(function (answer) { 
      myConnection.setLocalDescription(answer); 
      send({ 
         type: "answer", 
         answer: answer 
      }); 
		
   }, function (error) { 
      alert("oops...error"); 
   }); 
}
  
//when another user answers to our offer 
function onAnswer(answer) { 
   myConnection.setRemoteDescription(new RTCSessionDescription(answer)); 
} 
 
//when we got ice candidate from another user 
function onCandidate(candidate) { 
   myConnection.addIceCandidate(new RTCIceCandidate(candidate)); 
}

Puede ver que cuando un usuario hace clic en el botón "Establecer conexión", la aplicación hace una oferta de SDP al otro par. También configuramos los controladores onAnswer y onCandidate . Vuelva a cargar su página, ábrala en dos pestañas, inicie sesión con dos usuarios e intente establecer una conexión entre ellos. Debería ver la siguiente salida de la consola:

Ahora se establece la conexión de igual a igual. En los próximos tutoriales, agregaremos transmisiones de video y audio, así como soporte de chat de texto.

WebRTC no solo es bueno para transferir transmisiones de audio y video, sino también cualquier dato arbitrario que podamos tener. Aquí es donde entra en juego el objeto RTCDataChannel.

API RTCDataChannel

Propiedades

  • RTCDataChannel.label (read only) - Devuelve una cadena que contiene el nombre del canal de datos.

  • RTCDataChannel.ordered (read only) - Devuelve verdadero si el orden de entrega de los mensajes está garantizado o falso si no está garantizado.

  • RTCDataChannel.protocol (read only) - Devuelve una cadena que contiene el nombre del subprotocolo utilizado para este canal.

  • RTCDataChannel.id (read only) - Devuelve una identificación única para el canal que se estableció en la creación del objeto RTCDataChannel.

  • RTCDataChannel.readyState (read only)- Devuelve la enumeración RTCDataChannelState que representa el estado de la conexión. Los posibles valores -

    • connecting- Indica que la conexión aún no está activa. Este es el estado inicial.

    • open - Indica que la conexión se está ejecutando.

    • closing- Indica que la conexión está en proceso de cerrarse. Los mensajes en caché están en proceso de ser enviados o recibidos, pero no se acepta ninguna tarea recién creada.

    • closed - Indica que la conexión no se pudo establecer o se ha cerrado.

  • RTCDataChannel.bufferedAmount (read only)- Devuelve la cantidad de bytes que se han puesto en cola para su envío. Esta es la cantidad de datos que aún no se han enviado a través de RTCDataChannel.send ().

  • RTCDataChannel.bufferedAmountLowThreshold- Devuelve el número de bytes en los que RTCDataChannel.bufferedAmount se toma como mínimo. Cuando el RTCDataChannel.bufferedAmount disminuye por debajo de este umbral, se activa el evento bufferedamountlow.

  • RTCDataChannel.binaryType- Devuelve el tipo de datos binarios transmitidos por la conexión. Puede ser "blob" o "arraybuffer".

  • RTCDataChannel.maxPacketLifeType (read only) - Devuelve un corto sin firmar que indica la longitud en milisegundos de la ventana cuando la mensajería está en modo no confiable.

  • RTCDataChannel.maxRetransmits (read only) - Devuelve un corto sin firmar que indica el número máximo de veces que un canal retransmitirá datos si no se entregan.

  • RTCDataChannel.negotiated (read only) - Devuelve un booleano que indica si el canal ha sido negociado por el agente de usuario o por la aplicación.

  • RTCDataChannel.reliable (read only) - Devuelve un booleano que indica que la conexión puede enviar mensajes en modo no confiable.

  • RTCDataChannel.stream (read only) - Sinónimo de RTCDataChannel.id

Controladores de eventos

  • RTCDataChannel.onopen- Se llama a este controlador de eventos cuando se activa el evento abierto. Este evento se envía cuando se ha establecido la conexión de datos.

  • RTCDataChannel.onmessage- Se llama a este controlador de eventos cuando se activa el evento de mensaje. El evento se envía cuando hay un mensaje disponible en el canal de datos.

  • RTCDataChannel.onbufferedamountlow- Este controlador de eventos se llama cuando se activa el evento bufferedamoutlow. Este evento se envía cuando RTCDataChannel.bufferedAmount disminuye por debajo de la propiedad RTCDataChannel.bufferedAmountLowThreshold.

  • RTCDataChannel.onclose- Este controlador de eventos se llama cuando se activa el evento de cierre. Este evento se envía cuando se cierra la conexión de datos.

  • RTCDataChannel.onerror- Este controlador de eventos se llama cuando se activa el evento de error. Este evento se envía cuando se encuentra un error.

Métodos

  • RTCDataChannel.close() - Cierra el canal de datos.

  • RTCDataChannel.send()- Envía los datos del parámetro a través del canal. Los datos pueden ser un blob, una cadena, un ArrayBuffer o un ArrayBufferView.

Ahora creemos un ejemplo sencillo. En primer lugar, ejecute el servidor de señalización que creamos en el tutorial "servidor de señalización" a través de "servidor de nodo".

Habrá tres entradas de texto en la página, una para un inicio de sesión, otra para un nombre de usuario y otra para el mensaje que queremos enviar al otro par. Cree un archivo index.html y agregue el siguiente código:

<html lang = "en"> 
   <head> 
      <meta charset = "utf-8" /> 
   </head>
	
   <body> 
      <div> 
         <input type = "text" id = "loginInput" /> 
         <button id = "loginBtn">Login</button> 
      </div> 
		
      <div> 
         <input type = "text" id = "otherUsernameInput" /> 
         <button id = "connectToOtherUsernameBtn">Establish connection</button> 
      </div> 
		
      <div> 
         <input type = "text" id = "msgInput" /> 
         <button id = "sendMsgBtn">Send text message</button> 
      </div> 
		
      <script src = "client.js"></script>
   </body>
	
</html>

También agregamos tres botones para iniciar sesión, establecer una conexión y enviar un mensaje. Ahora cree un archivo client.js y agregue el siguiente código:

var connection = new WebSocket('ws://localhost:9090'); 
var name = "";

var loginInput = document.querySelector('#loginInput'); 
var loginBtn = document.querySelector('#loginBtn'); 

var otherUsernameInput = document.querySelector('#otherUsernameInput'); 
var connectToOtherUsernameBtn = document.querySelector('#connectToOtherUsernameBtn'); 
var msgInput = document.querySelector('#msgInput'); 
var sendMsgBtn = document.querySelector('#sendMsgBtn'); 
var connectedUser, myConnection, dataChannel;
  
//when a user clicks the login button 
loginBtn.addEventListener("click", function(event) { 
   name = loginInput.value; 
	
   if(name.length > 0) { 
      send({ 
         type: "login", 
         name: name 
      }); 
   } 
}); 
 
//handle messages from the server 
connection.onmessage = function (message) { 
   console.log("Got message", message.data); 
   var data = JSON.parse(message.data); 
	
   switch(data.type) { 
      case "login": 
         onLogin(data.success); 
         break; 
      case "offer": 
         onOffer(data.offer, data.name); 
         break; 
      case "answer":
         onAnswer(data.answer); 
         break; 
      case "candidate": 
         onCandidate(data.candidate); 
         break; 
      default: 
         break; 
   } 
}; 
 
//when a user logs in 
function onLogin(success) { 

   if (success === false) { 
      alert("oops...try a different username"); 
   } else { 
      //creating our RTCPeerConnection object 
      var configuration = { 
         "iceServers": [{ "url": "stun:stun.1.google.com:19302" }] 
      }; 
		
      myConnection = new webkitRTCPeerConnection(configuration, { 
         optional: [{RtpDataChannels: true}] 
      }); 
		
      console.log("RTCPeerConnection object was created"); 
      console.log(myConnection); 
  
      //setup ice handling 
      //when the browser finds an ice candidate we send it to another peer 
      myConnection.onicecandidate = function (event) { 
		
         if (event.candidate) { 
            send({ 
               type: "candidate", 
               candidate: event.candidate 
            });
         } 
      }; 
		
      openDataChannel();
		
   } 
};
  
connection.onopen = function () { 
   console.log("Connected"); 
}; 
 
connection.onerror = function (err) { 
   console.log("Got error", err); 
};
  
// Alias for sending messages in JSON format 
function send(message) { 
   if (connectedUser) { 
      message.name = connectedUser; 
   }
	
   connection.send(JSON.stringify(message)); 
};

Puede ver que establecemos una conexión de socket a nuestro servidor de señalización. Cuando un usuario hace clic en el botón de inicio de sesión, la aplicación envía su nombre de usuario al servidor. Si el inicio de sesión es exitoso, la aplicación crea el objeto RTCPeerConnection y configura un controlador de candidatos de hielo que envía todos los candidatos de hielo encontrados al otro par. También ejecuta la función openDataChannel () que crea un dataChannel. Tenga en cuenta que al crear el objeto RTCPeerConnection, el segundo argumento en el constructor es opcional: [{RtpDataChannels: true}] es obligatorio si está utilizando Chrome u Opera. El siguiente paso es crear una oferta para el otro compañero. Agregue el siguiente código a su archivo client.js

//setup a peer connection with another user 
connectToOtherUsernameBtn.addEventListener("click", function () {
  
   var otherUsername = otherUsernameInput.value;
   connectedUser = otherUsername;
	
   if (otherUsername.length > 0) { 
      //make an offer 
      myConnection.createOffer(function (offer) { 
         console.log(); 
         send({ 
            type: "offer", 
            offer: offer 
         }); 
			
         myConnection.setLocalDescription(offer); 
      }, function (error) { 
         alert("An error has occurred."); 
      }); 
   } 
});
  
//when somebody wants to call us 
function onOffer(offer, name) { 
   connectedUser = name; 
   myConnection.setRemoteDescription(new RTCSessionDescription(offer));
	
   myConnection.createAnswer(function (answer) { 
      myConnection.setLocalDescription(answer); 
		
      send({ 
         type: "answer", 
         answer: answer 
      }); 
		
   }, function (error) { 
      alert("oops...error"); 
   }); 
}

//when another user answers to our offer 
function onAnswer(answer) { 
   myConnection.setRemoteDescription(new RTCSessionDescription(answer)); 
}
  
//when we got ice candidate from another user 
function onCandidate(candidate) { 
   myConnection.addIceCandidate(new RTCIceCandidate(candidate)); 
}

Puede ver que cuando un usuario hace clic en el botón "Establecer conexión", la aplicación hace una oferta de SDP al otro par. También configuramos los controladores onAnswer y onCandidate . Finalmente, implementemos la función openDataChannel () que crea nuestro dataChannel. Agregue el siguiente código a su archivo client.js :

//creating data channel 
function openDataChannel() { 

   var dataChannelOptions = { 
      reliable:true 
   }; 
	
   dataChannel = myConnection.createDataChannel("myDataChannel", dataChannelOptions);
	
   dataChannel.onerror = function (error) { 
      console.log("Error:", error); 
   };
	
   dataChannel.onmessage = function (event) { 
      console.log("Got message:", event.data); 
   };  
}
  
//when a user clicks the send message button 
sendMsgBtn.addEventListener("click", function (event) { 
   console.log("send message");
   var val = msgInput.value; 
   dataChannel.send(val); 
});

Aquí creamos el canal de datos para nuestra conexión y agregamos el controlador de eventos para el botón "enviar mensaje". Ahora abra esta página en dos pestañas, inicie sesión con dos usuarios, establezca una conexión e intente enviar mensajes. Debería verlos en la salida de la consola. Tenga en cuenta que el ejemplo anterior se prueba en Opera.

Ahora puede ver que RTCDataChannel es una parte extremadamente poderosa de la API de WebRTC. Hay muchos otros casos de uso para este objeto, como los juegos de igual a igual o el intercambio de archivos basado en torrents.

La mayoría de las aplicaciones WebRTC no solo se pueden comunicar a través de video y audio. Necesitan muchas otras funciones. En este capítulo, vamos a construir un servidor de señalización básico.

Señalización y negociación

Para conectarse con otro usuario debe saber dónde se encuentra en la Web. La dirección IP de su dispositivo permite que los dispositivos habilitados para Internet se envíen datos directamente entre sí. El objeto RTCPeerConnection es responsable de esto. Tan pronto como los dispositivos saben cómo encontrarse entre sí a través de Internet, comienzan a intercambiar datos sobre qué protocolos y códecs admite cada dispositivo.

Para comunicarse con otro usuario, simplemente necesita intercambiar información de contacto y el resto lo hará WebRTC. El proceso de conexión con el otro usuario también se conoce como señalización y negociación. Consta de unos pocos pasos:

  • Cree una lista de posibles candidatos para una conexión entre pares.

  • El usuario o una aplicación selecciona un usuario con el que establecer una conexión.

  • La capa de señalización notifica a otro usuario que alguien quiere conectarse con él. Puede aceptarlo o rechazarlo.

  • Se notifica al primer usuario la aceptación de la oferta.

  • El primer usuario inicia RTCPeerConnection con otro usuario.

  • Ambos usuarios intercambian información de software y hardware a través del servidor de señalización.

  • Ambos usuarios intercambian información de ubicación.

  • La conexión tiene éxito o falla.

La especificación WebRTC no contiene ningún estándar sobre el intercambio de información. Así que tenga en cuenta que lo anterior es solo un ejemplo de cómo puede ocurrir la señalización. Puede utilizar cualquier protocolo o tecnología que desee.

Construyendo el servidor

El servidor que vamos a construir podrá conectar dos usuarios juntos que no estén ubicados en la misma computadora. Crearemos nuestro propio mecanismo de señalización. Nuestro servidor de señalización permitirá que un usuario llame a otro. Una vez que un usuario ha llamado a otro, el servidor pasa la oferta, responde, candidatos ICE entre ellos y establece una conexión WebRTC.

El diagrama anterior es el flujo de mensajes entre usuarios cuando utilizan el servidor de señalización. En primer lugar, cada usuario se registra en el servidor. En nuestro caso, será un nombre de usuario de cadena simple. Una vez que los usuarios se han registrado, pueden llamarse entre sí. El usuario 1 hace una oferta con el identificador de usuario al que desea llamar. El otro usuario debería responder. Finalmente, los candidatos de ICE se envían entre usuarios hasta que puedan establecer una conexión.

Para crear una conexión WebRTC, los clientes deben poder transferir mensajes sin utilizar una conexión de pares WebRTC. Aquí es donde usaremos HTML5 WebSockets, una conexión de socket bidireccional entre dos puntos finales, un servidor web y un navegador web. Ahora comencemos a usar la biblioteca WebSocket. Cree el archivo server.js e inserte el siguiente código:

//require our websocket library 
var WebSocketServer = require('ws').Server; 

//creating a websocket server at port 9090 
var wss = new WebSocketServer({port: 9090}); 
 
//when a user connects to our sever 
wss.on('connection', function(connection) { 
   console.log("user connected");
	
   //when server gets a message from a connected user 
   connection.on('message', function(message){ 
      console.log("Got message from a user:", message); 
   }); 
	
   connection.send("Hello from server"); 
});

La primera línea requiere la biblioteca WebSocket que ya hemos instalado. Luego creamos un servidor de socket en el puerto 9090. Luego, escuchamos el evento de conexión . Este código se ejecutará cuando un usuario establezca una conexión WebSocket al servidor. Luego escuchamos los mensajes enviados por el usuario. Finalmente, enviamos una respuesta al usuario conectado diciendo "Hola desde el servidor".

Ahora ejecute el servidor de nodo y el servidor debería comenzar a escuchar conexiones de socket.

Para probar nuestro servidor, usaremos la utilidad wscat que también tenemos instalada. Esta herramienta ayuda a conectarse directamente al servidor WebSocket y a probar los comandos. Ejecute nuestro servidor en una ventana de terminal, luego abra otra y ejecute el comando wscat -c ws: // localhost: 9090 . Debería ver lo siguiente en el lado del cliente:

El servidor también debe registrar al usuario conectado:

registro de usuario

En nuestro servidor de señalización, usaremos un nombre de usuario basado en cadenas para cada conexión para que sepamos dónde enviar mensajes. Cambiemos un poco nuestro controlador de conexión :

connection.on('message', function(message) { 
   var data; 
	
   //accepting only JSON messages 
   try { 
      data = JSON.parse(message); 
   } catch (e) { 
      console.log("Invalid JSON"); 
      data = {}; 
   } 
	
});

De esta forma solo aceptamos mensajes JSON. A continuación, necesitamos almacenar a todos los usuarios conectados en algún lugar. Usaremos un objeto Javascript simple para ello. Cambie la parte superior de nuestro archivo -

//require our websocket library 
var WebSocketServer = require('ws').Server;
 
//creating a websocket server at port 9090 
var wss = new WebSocketServer({port: 9090}); 

//all connected to the server users
var users = {};

Vamos a agregar un campo de tipo para cada mensaje proveniente del cliente. Por ejemplo, si un usuario desea iniciar sesión, envía el mensaje de tipo de inicio de sesión . Vamos a definirlo -

connection.on('message', function(message){
   var data; 
	
   //accepting only JSON messages 
   try { 
      data = JSON.parse(message); 
   } catch (e) { 
      console.log("Invalid JSON"); 
      data = {}; 
   }
	
   //switching type of the user message 
   switch (data.type) { 
      //when a user tries to login 
      case "login": 
         console.log("User logged:", data.name); 
			
         //if anyone is logged in with this username then refuse 
         if(users[data.name]) { 
            sendTo(connection, { 
               type: "login", 
               success: false 
            }); 
         } else { 
            //save user connection on the server 
            users[data.name] = connection; 
            connection.name = data.name; 
				
            sendTo(connection, { 
               type: "login", 
               success: true 
            });
				
         } 
			
         break;
					 
      default: 
         sendTo(connection, { 
            type: "error", 
            message: "Command no found: " + data.type 
         }); 
			
         break; 
   } 
	
});

Si el usuario envía un mensaje con el tipo de inicio de sesión , nosotros:

  • Compruebe si alguien ya ha iniciado sesión con este nombre de usuario

  • Si es así, dígale al usuario que no ha iniciado sesión correctamente.

  • Si nadie está usando este nombre de usuario, agregamos el nombre de usuario como clave para el objeto de conexión.

  • Si no se reconoce un comando, enviamos un error.

El siguiente código es una función auxiliar para enviar mensajes a una conexión. Añadirlo a la server.js archivo -

function sendTo(connection, message) { 
   connection.send(JSON.stringify(message)); 
}

La función anterior asegura que todos nuestros mensajes se envíen en formato JSON.

Cuando el usuario se desconecta debemos limpiar su conexión. Podemos eliminar al usuario cuando se dispara el evento de cierre . Agregue el siguiente código al controlador de conexión :

connection.on("close", function() { 
   if(connection.name) { 
      delete users[connection.name]; 
    } 
});

Ahora probemos nuestro servidor con el comando de inicio de sesión. Tenga en cuenta que todos los mensajes deben estar codificados en formato JSON. Ejecute nuestro servidor e intente iniciar sesión. Deberías ver algo como esto:

Haciendo una llamada

Después de iniciar sesión correctamente, el usuario desea llamar a otro. Debería hacer una oferta a otro usuario para lograrlo. Agregar el controlador de ofertas -

case "offer": 
   //for ex. UserA wants to call UserB 
   console.log("Sending offer to: ", data.name); 
	
   //if UserB exists then send him offer details 
   var conn = users[data.name]; 
	
   if(conn != null){ 
      //setting that UserA connected with UserB 
      connection.otherName = data.name; 
		
      sendTo(conn, { 
         type: "offer", 
         offer: data.offer, 
         name: connection.name 
      }); 
   }
	
   break;

En primer lugar, obtenemos la conexión del usuario al que intentamos llamar. Si existe le enviamos los detalles de la oferta . También agregamos otherName al objeto de conexión . Esto se hace por la simplicidad de encontrarlo más tarde.

Respondiendo

Responder a la respuesta tiene un patrón similar al que usamos en el controlador de ofertas . Nuestro servidor simplemente pasa todos los mensajes como respuesta a otro usuario. Agregue el siguiente código después de la mano de oferta :

case "answer": 
   console.log("Sending answer to: ", data.name); 
	
   //for ex. UserB answers UserA 
   var conn = users[data.name]; 
	
   if(conn != null) { 
      connection.otherName = data.name; 
      sendTo(conn, { 
         type: "answer", 
         answer: data.answer 
      }); 
   }
	
   break;

Puede ver cómo esto es similar al controlador de ofertas . Observe que este código sigue a las funciones createOffer y createAnswer en el objeto RTCPeerConnection .

Ahora podemos probar nuestro mecanismo de oferta / respuesta. Conecta dos clientes al mismo tiempo e intenta hacer una oferta y una respuesta. Debería ver lo siguiente:

En este ejemplo, offer y answer son cadenas simples, pero en una aplicación real se completarán con los datos de SDP.

Candidatos ICE

La parte final es el manejo del candidato ICE entre usuarios. Usamos la misma técnica simplemente pasando mensajes entre usuarios. La principal diferencia es que los mensajes candidatos pueden aparecer varias veces por usuario en cualquier orden. Agregar el controlador de candidatos -

case "candidate": 
   console.log("Sending candidate to:",data.name); 
   var conn = users[data.name]; 
	
   if(conn != null) {
      sendTo(conn, { 
         type: "candidate", 
         candidate: data.candidate 
      }); 
   }
	
   break;

Debería funcionar de manera similar a los controladores de ofertas y respuestas .

Dejar la conexión

Para permitir que nuestros usuarios se desconecten de otro usuario, debemos implementar la función de colgar. También le indicará al servidor que elimine todas las referencias de usuario. Añade elleave manejador -

case "leave": 
   console.log("Disconnecting from", data.name); 
   var conn = users[data.name]; 
   conn.otherName = null; 
	
   //notify the other user so he can disconnect his peer connection 
   if(conn != null) { 
      sendTo(conn, { 
         type: "leave" 
      }); 
   } 
	
   break;

Esto también enviará al otro usuario el evento de licencia para que pueda desconectar su conexión de igual en consecuencia. También deberíamos manejar el caso cuando un usuario pierde su conexión del servidor de señalización. Modifiquemos nuestro controlador cercano -

connection.on("close", function() { 

   if(connection.name) { 
      delete users[connection.name]; 
		
      if(connection.otherName) { 
         console.log("Disconnecting from ", connection.otherName); 
         var conn = users[connection.otherName]; 
         conn.otherName = null;
			
         if(conn != null) { 
            sendTo(conn, { 
               type: "leave" 
            }); 
         }  
      } 
   } 
});

Ahora, si la conexión termina, nuestros usuarios serán desconectados. El evento de cierre se activará cuando un usuario cierre la ventana de su navegador mientras todavía estamos en estado de oferta , respuesta o candidato .

Servidor de señalización completo

Aquí está el código completo de nuestro servidor de señalización:

//require our websocket library 
var WebSocketServer = require('ws').Server;
 
//creating a websocket server at port 9090 
var wss = new WebSocketServer({port: 9090}); 

//all connected to the server users 
var users = {};
  
//when a user connects to our sever 
wss.on('connection', function(connection) {
  
   console.log("User connected");
	
   //when server gets a message from a connected user
   connection.on('message', function(message) { 
	
      var data; 
      //accepting only JSON messages 
      try {
         data = JSON.parse(message); 
      } catch (e) { 
         console.log("Invalid JSON"); 
         data = {}; 
      } 
		
      //switching type of the user message 
      switch (data.type) { 
         //when a user tries to login 
			
         case "login": 
            console.log("User logged", data.name); 
				
            //if anyone is logged in with this username then refuse 
            if(users[data.name]) { 
               sendTo(connection, { 
                  type: "login", 
                  success: false 
               }); 
            } else { 
               //save user connection on the server 
               users[data.name] = connection; 
               connection.name = data.name; 
               sendTo(connection, { 
                  type: "login", 
                  success: true 
               }); 
            } 
				
            break; 
				
         case "offer": 
            //for ex. UserA wants to call UserB 
            console.log("Sending offer to: ", data.name); 
				
            //if UserB exists then send him offer details 
            var conn = users[data.name];
				
            if(conn != null) { 
               //setting that UserA connected with UserB 
               connection.otherName = data.name; 
					
               sendTo(conn, { 
                  type: "offer", 
                  offer: data.offer, 
                  name: connection.name 
               }); 
            } 
				
            break;  
				
         case "answer": 
            console.log("Sending answer to: ", data.name); 
            //for ex. UserB answers UserA 
            var conn = users[data.name]; 
				
            if(conn != null) { 
               connection.otherName = data.name; 
               sendTo(conn, { 
                  type: "answer", 
                  answer: data.answer 
               }); 
            } 
				
            break;  
				
         case "candidate": 
            console.log("Sending candidate to:",data.name); 
            var conn = users[data.name];  
				
            if(conn != null) { 
               sendTo(conn, { 
                  type: "candidate", 
                  candidate: data.candidate 
               });
            } 
				
            break;  
				
         case "leave": 
            console.log("Disconnecting from", data.name); 
            var conn = users[data.name]; 
            conn.otherName = null; 
				
            //notify the other user so he can disconnect his peer connection 
            if(conn != null) { 
               sendTo(conn, { 
                  type: "leave" 
               }); 
            }  
				
            break;  
				
         default: 
            sendTo(connection, { 
               type: "error", 
               message: "Command not found: " + data.type 
            }); 
				
            break; 
      }  
   });  
	
   //when user exits, for example closes a browser window 
   //this may help if we are still in "offer","answer" or "candidate" state 
   connection.on("close", function() { 
	
      if(connection.name) { 
      delete users[connection.name]; 
		
         if(connection.otherName) { 
            console.log("Disconnecting from ", connection.otherName);
            var conn = users[connection.otherName]; 
            conn.otherName = null;  
				
            if(conn != null) { 
               sendTo(conn, { 
                  type: "leave" 
               });
            }  
         } 
      } 
   });  
	
   connection.send("Hello world"); 
	
});  

function sendTo(connection, message) { 
   connection.send(JSON.stringify(message)); 
}

Así que el trabajo está hecho y nuestro servidor de señalización está listo. Recuerde que hacer cosas fuera de orden al realizar una conexión WebRTC puede causar problemas.

Resumen

En este capítulo, creamos un servidor de señalización simple y directo. Recorrimos el proceso de señalización, el registro de usuarios y el mecanismo de oferta / respuesta. También implementamos el envío de candidatos entre usuarios.

La Web se mueve muy rápido y siempre está mejorando. Todos los días se crean nuevos estándares. Los navegadores permiten que las actualizaciones se instalen sin que el usuario lo sepa, por lo que debe mantenerse al tanto de lo que sucede en el mundo de la Web y WebRTC. Aquí hay una descripción general de lo que esto está haciendo hoy.

Soporte del navegador

No todos los navegadores tienen las mismas funciones de WebRTC al mismo tiempo. Es posible que diferentes navegadores estén a la vanguardia, lo que hace que algunas características de WebRTC funcionen en un navegador y no en otro. El soporte actual para WebRTC en el navegador se muestra en la siguiente imagen.

Puede comprobar el estado de soporte de WebRTC actualizado en http://caniuse.com/#feat=rtcpeerconnection.

Chrome, Firefox y Opera

Las últimas versiones de Chrome, Firefox y Opera en los sistemas operativos de PC convencionales, como Mac OS X, Windows y Linux, son compatibles con WebRTC desde el primer momento. Y lo más importante, los ingenieros de los equipos de desarrolladores de Chrome y Firefox han estado trabajando juntos para solucionar problemas de modo que estos dos navegadores puedan comunicarse entre sí fácilmente.

SO Android

En los sistemas operativos Android, las aplicaciones WebRTC para Chrome y Firefox deberían funcionar de forma inmediata. Pueden funcionar con otros navegadores después de la versión de Android Ice Cream Sandwich (4.0). Esto se debe al uso compartido de código entre las versiones de escritorio y móviles.

manzana

Apple aún no ha hecho ningún anuncio sobre sus planes para admitir WebRTC en Safari en OS X. Una de las posibles soluciones para las aplicaciones iOS nativas híbridas es incrustar el código WebRTC directamente en la aplicación y cargar esta aplicación en un WebView.

explorador de Internet

Microsoft no admite WebRTC en computadoras de escritorio. Pero han confirmado oficialmente que van a implementar ORTC (Object Realtime Communications) en futuras versiones de IE (Edge). No planean admitir WebRTC 1.0. Etiquetaron su ORTC como WebRTC 1.1, aunque es solo una mejora de la comunidad y no el estándar oficial. Recientemente, agregaron el soporte ORTC a la última versión de Microsoft Edge. Puede aprender más enhttps://blogs.windows.com/msedgedev/2015/09/18/ortc-api-is-now-available-in-microsoftedge/.

Resumen

Tenga en cuenta que WebRTC es una colección de API y protocolos, no una sola API. El soporte para cada uno de estos se está desarrollando en diferentes navegadores y sistemas operativos a un nivel diferente. Una excelente manera de verificar el último nivel de soporte es a través dehttp://canisue.com.Realiza un seguimiento de la adopción de API modernas en varios navegadores. También puede encontrar la información más reciente sobre compatibilidad con navegadores, así como demostraciones de WebRTC enhttp://www.webrtc.org, que es compatible con Mozilla, Google y Opera.

En el mundo móvil, el soporte de WebRTC no está al mismo nivel que en los equipos de escritorio. Los dispositivos móviles tienen su propio camino, por lo que WebRTC también es algo diferente en las plataformas móviles.

Al desarrollar una aplicación WebRTC para escritorio, consideramos usar Chrome, Firefox u Opera. Todos ellos admiten WebRTC desde el primer momento. En general, solo necesita un navegador y no preocuparse por el hardware del escritorio.

En el mundo móvil, existen tres modos posibles para WebRTC en la actualidad:

  • La aplicación nativa
  • La aplicación del navegador
  • El navegador nativo

Androide

En 2013, el navegador web Firefox para Android se presentó con soporte WebRTC listo para usar. Ahora puede realizar videollamadas en dispositivos Android utilizando el navegador móvil Firefox.

Tiene tres componentes principales de WebRTC:

  • PeerConnection - habilita llamadas entre navegadores

  • getUserMedia - proporciona acceso a la cámara y al micrófono

  • DataChannels - proporciona transferencia de datos peer-to-peer

Google Chrome para Android también proporciona compatibilidad con WebRTC. Como ya habrás notado, las funciones más interesantes suelen aparecer por primera vez en Chrome.

El año pasado, apareció el navegador móvil Opera con soporte WebRTC. Entonces, para Android tienes Chrome, Firefox y Opera. Otros navegadores no son compatibles con WebRTC.

iOS

Desafortunadamente, WebRTC ahora no es compatible con iOS. Aunque WebRTC funciona bien en Mac cuando se usa Firefox, Opera o Chrome, no es compatible con iOS.

Hoy en día, su aplicación WebRTC no funcionará en los dispositivos móviles de Apple de forma inmediata. Pero hay un navegador: Bowser. Es un navegador web desarrollado por Ericsson y es compatible con WebRTC listo para usar. Puedes consultar su página de inicio enhttp://www.openwebrtc.org/bowser/.

Hoy en día, es la única forma amigable de admitir su aplicación WebRTC en iOS. Otra forma es desarrollar una aplicación nativa usted mismo.

Teléfonos Windows

Microsoft no admite WebRTC en plataformas móviles. Pero han confirmado oficialmente que van a implementar ORTC (Object Realtime Communications) en futuras versiones de IE. No planean admitir WebRTC 1.0. Etiquetaron su ORTC como WebRTC 1.1, aunque es solo una mejora de la comunidad y no el estándar oficial.

Así que hoy los usuarios de Windows Phone no pueden usar aplicaciones WebRTC y no hay forma de superar esta situación.

Mora

Las aplicaciones WebRTC tampoco son compatibles con Blackberry, de ninguna manera.

Uso de un navegador nativo de WebRTC

El caso más conveniente y cómodo para que los usuarios utilicen WebRTC es utilizar el navegador nativo del dispositivo. En este caso, el dispositivo está listo para trabajar con cualquier configuración adicional.

Hoy en día, solo los dispositivos Android de la versión 4 o superior ofrecen esta función. Apple todavía no muestra ninguna actividad con el soporte de WebRTC. Por tanto, los usuarios de Safari no pueden utilizar aplicaciones WebRTC. Microsoft tampoco lo introdujo en Windows Phone 8.

Uso de WebRTC a través de aplicaciones de navegador

Esto significa utilizar aplicaciones de terceros (navegadores web no nativos) para proporcionar las funciones de WebRTC. Por ahora, existen dos aplicaciones de terceros de este tipo. Bowser, que es la única forma de llevar las funciones de WebRTC al dispositivo iOS y Opera, que es una buena alternativa para la plataforma Android. El resto de los navegadores móviles disponibles no son compatibles con WebRTC.

Aplicaciones móviles nativas

Como puede ver, WebRTC aún no tiene un gran soporte en el mundo móvil. Entonces, una de las posibles soluciones es desarrollar aplicaciones nativas que utilicen la API WebRTC. Pero no es la mejor opción porque la característica principal de WebRTC es una solución multiplataforma. De todos modos, en algunos casos esta es la única forma porque una aplicación nativa puede utilizar funciones específicas del dispositivo o características que no son compatibles con los navegadores HTML5.

Restricción de la transmisión de video para dispositivos móviles y de escritorio

El primer parámetro de la API getUserMedia espera un objeto de claves y valores que le indique al navegador cómo procesar los flujos. Puede consultar el conjunto completo de restricciones enhttps://tools.ietf.org/html/draft-alvestrand-constraints-resolution-03. Puede configurar la relación de aspecto de video, frameRate y otros parámetros opcionales.

La compatibilidad con dispositivos móviles es uno de los mayores problemas porque los dispositivos móviles tienen un espacio de pantalla limitado y recursos limitados. Es posible que desee que el dispositivo móvil solo capture una resolución de 480x320 o una transmisión de video más pequeña para ahorrar energía y ancho de banda. El uso de la cadena del agente de usuario en el navegador es una buena forma de comprobar si el usuario está en un dispositivo móvil o no. Veamos un ejemplo. Cree el archivo index.html -

<!DOCTYPE html> 
<html lang = "en">
 
   <head> 
      <meta charset = "utf-8" /> 
   </head> 
	
   <body> 
      <video autoplay></video> 
      <script src = "client.js"></script> 
   </body>
	
</html>

Luego crea el siguiente archivo client.js :

//constraints for desktop browser 
var desktopConstraints = { 

   video: { 
      mandatory: { 
         maxWidth:800,
         maxHeight:600   
      }  
   }, 
	
   audio: true 
}; 
 
//constraints for mobile browser 
var mobileConstraints = { 

   video: { 
      mandatory: { 
         maxWidth: 480, 
         maxHeight: 320, 
      } 
   }, 
	
   audio: true 
}
  
//if a user is using a mobile browser 
if(/Android|iPhone|iPad/i.test(navigator.userAgent)) { 
   var constraints = mobileConstraints;   
} else { 
   var constraints = desktopConstraints; 
}
  
function hasUserMedia() { 
   //check if the browser supports the WebRTC 
   return !!(navigator.getUserMedia || navigator.webkitGetUserMedia || 
      navigator.mozGetUserMedia); 
}
  
if (hasUserMedia()) {
  
   navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || 
      navigator.mozGetUserMedia;
	
   //enabling video and audio channels 
   navigator.getUserMedia(constraints, function (stream) { 
      var video = document.querySelector('video');
		
      //inserting our stream to the video tag     
      video.src = window.URL.createObjectURL(stream);
		
   }, function (err) {}); 
} else { 
   alert("WebRTC is not supported"); 
}

Ejecute el servidor web con el comando estático y abra la página. Debería ver que es 800x600. Luego abra esta página en una ventana gráfica móvil usando herramientas de Chrome y verifique la resolución. Debería ser 480x320.

Las restricciones son la forma más sencilla de aumentar el rendimiento de su aplicación WebRTC.

Resumen

En este capítulo, aprendimos sobre los problemas que pueden ocurrir al desarrollar aplicaciones WebRTC para dispositivos móviles. Descubrimos diferentes limitaciones de la compatibilidad con la API de WebRTC en plataformas móviles. También lanzamos una aplicación de demostración en la que establecemos diferentes restricciones para navegadores de escritorio y móviles.

En este capítulo, vamos a crear una aplicación cliente que permite que dos usuarios en dispositivos separados se comuniquen usando WebRTC. Nuestra aplicación tendrá dos páginas. Uno para iniciar sesión y el otro para llamar a otro usuario.

Las dos páginas serán las etiquetas div . La mayor parte de la entrada se realiza a través de controladores de eventos simples.

Servidor de señalización

Para crear una conexión WebRTC, los clientes deben poder transferir mensajes sin utilizar una conexión de pares WebRTC. Aquí es donde usaremos HTML5 WebSockets, una conexión de socket bidireccional entre dos puntos finales, un servidor web y un navegador web. Ahora comencemos a usar la biblioteca WebSocket. Cree el archivo server.js e inserte el siguiente código:

//require our websocket library 
var WebSocketServer = require('ws').Server; 

//creating a websocket server at port 9090 
var wss = new WebSocketServer({port: 9090});
  
//when a user connects to our sever 
wss.on('connection', function(connection) { 
   console.log("user connected"); 
	
   //when server gets a message from a connected user 
   connection.on('message', function(message) { 
      console.log("Got message from a user:", message); 
   }); 
	
   connection.send("Hello from server");
});

La primera línea requiere la biblioteca WebSocket que ya hemos instalado. Luego creamos un servidor de socket en el puerto 9090. Luego, escuchamos el evento de conexión . Este código se ejecutará cuando un usuario establezca una conexión WebSocket al servidor. Luego escuchamos los mensajes enviados por el usuario. Finalmente, enviamos una respuesta al usuario conectado diciendo "Hola desde el servidor".

En nuestro servidor de señalización, usaremos un nombre de usuario basado en cadenas para cada conexión para que sepamos dónde enviar mensajes. Cambiemos un poco nuestro controlador de conexión :

connection.on('message', function(message) { 
   var data; 
	
   //accepting only JSON messages 
   try { 
      data = JSON.parse(message); 
   } catch (e) { 
      console.log("Invalid JSON"); 
      data = {}; 
   } 
});

De esta forma solo aceptamos mensajes JSON. A continuación, necesitamos almacenar a todos los usuarios conectados en algún lugar. Usaremos un objeto Javascript simple para ello. Cambie la parte superior de nuestro archivo -

//require our websocket library 
var WebSocketServer = require('ws').Server; 

//creating a websocket server at port 9090 
var wss = new WebSocketServer({port: 9090}); 

//all connected to the server users 
var users = {};

Vamos a agregar un campo de tipo para cada mensaje proveniente del cliente. Por ejemplo, si un usuario desea iniciar sesión, envía el mensaje de tipo de inicio de sesión . Vamos a definirlo -

connection.on('message', function(message) { 
   var data; 
	
   //accepting only JSON messages 
   try { 
      data = JSON.parse(message);
   } catch (e) { 
      console.log("Invalid JSON"); 
      data = {}; 
   } 
	
   //switching type of the user message 
   switch (data.type) { 
      //when a user tries to login 
      case "login": 
         console.log("User logged:", data.name); 
			
         //if anyone is logged in with this username then refuse 
         if(users[data.name]) { 
            sendTo(connection, { 
               type: "login", 
               success: false 
            }); 
         } else { 
            //save user connection on the server 
            users[data.name] = connection; 
            connection.name = data.name; 
				
            sendTo(connection, { 
               type: "login", 
               success: true 
            }); 
         } 
			
         break;
			
      default: 
         sendTo(connection, { 
            type: "error", 
            message: "Command no found: " + data.type 
         }); 
			
         break;
   }  
});

Si el usuario envía un mensaje con el tipo de inicio de sesión , nosotros:

  • Compruebe si alguien ya ha iniciado sesión con este nombre de usuario

  • Si es así, dígale al usuario que no ha iniciado sesión correctamente.

  • Si nadie está usando este nombre de usuario, agregamos el nombre de usuario como clave para el objeto de conexión.

  • Si no se reconoce un comando, enviamos un error.

El siguiente código es una función auxiliar para enviar mensajes a una conexión. Añadirlo a la server.js archivo -

function sendTo(connection, message) { 
   connection.send(JSON.stringify(message)); 
}

Cuando el usuario se desconecta debemos limpiar su conexión. Podemos eliminar al usuario cuando se dispara el evento de cierre . Agregue el siguiente código al controlador de conexión :

connection.on("close", function() { 
   if(connection.name) { 
      delete users[connection.name]; 
   } 
});

Después de iniciar sesión correctamente, el usuario desea llamar a otro. Debería hacer una oferta a otro usuario para lograrlo. Agregar el controlador de ofertas -

case "offer": 
   //for ex. UserA wants to call UserB 
   console.log("Sending offer to: ", data.name);
	
   //if UserB exists then send him offer details 
   var conn = users[data.name]; 
	
   if(conn != null) { 
      //setting that UserA connected with UserB 
      connection.otherName = data.name; 
		
      sendTo(conn, { 
         type: "offer",
         offer: data.offer, 
         name: connection.name 
      }); 
		
   }  
	
   break;

En primer lugar, obtenemos la conexión del usuario al que intentamos llamar. Si existe le enviamos los detalles de la oferta . También agregamos otherName al objeto de conexión . Esto se hace por la simplicidad de encontrarlo más tarde.

Responder a la respuesta tiene un patrón similar al que usamos en el controlador de ofertas . Nuestro servidor simplemente pasa todos los mensajes como respuesta a otro usuario. Agregue el siguiente código después del controlador de ofertas :

case "answer": 
   console.log("Sending answer to: ", data.name); 
	
   //for ex. UserB answers UserA 
   var conn = users[data.name]; 
	
   if(conn != null) { 
      connection.otherName = data.name; 
		
      sendTo(conn, { 
         type: "answer", 
         answer: data.answer 
      }); 
   } 
	
   break;

La parte final es el manejo del candidato ICE entre usuarios. Usamos la misma técnica simplemente pasando mensajes entre usuarios. La principal diferencia es que los mensajes candidatos pueden aparecer varias veces por usuario en cualquier orden. Agregar el controlador de candidatos -

case "candidate": 
   console.log("Sending candidate to:",data.name); 
   var conn = users[data.name];
	
   if(conn != null) { 
      sendTo(conn, { 
         type: "candidate", 
         candidate: data.candidate 
      }); 
   } 
	
   break;

Para permitir que nuestros usuarios se desconecten de otro usuario, debemos implementar la función de colgar. También le indicará al servidor que elimine todas las referencias de usuario. Agregue el controlador de licencia -

case "leave": 
   console.log("Disconnecting from", data.name); 
   var conn = users[data.name]; 
   conn.otherName = null; 
	
   //notify the other user so he can disconnect his peer connection 
   if(conn != null) { 
      sendTo(conn, { 
         type: "leave" 
      }); 
   } 
	
   break;

Esto también enviará al otro usuario el evento de licencia para que pueda desconectar su conexión de igual en consecuencia. También deberíamos manejar el caso cuando un usuario pierde su conexión del servidor de señalización. Modifiquemos nuestro controlador cercano -

connection.on("close", function() { 

   if(connection.name) { 
      delete users[connection.name]; 
		
      if(connection.otherName) { 
         console.log("Disconnecting from ", connection.otherName); 
         var conn = users[connection.otherName]; 
         conn.otherName = null;  
			
         if(conn != null) { 
            sendTo(conn, { 
               type: "leave" 
            });
         }
			
      } 
   } 
});

El siguiente es el código completo de nuestro servidor de señalización:

//require our websocket library 
var WebSocketServer = require('ws').Server; 

//creating a websocket server at port 9090 
var wss = new WebSocketServer({port: 9090}); 

//all connected to the server users 
var users = {};
  
//when a user connects to our sever 
wss.on('connection', function(connection) {
  
   console.log("User connected");
	
   //when server gets a message from a connected user 
   connection.on('message', function(message) { 
	
      var data; 
		
      //accepting only JSON messages 
      try { 
         data = JSON.parse(message); 
      } catch (e) { 
         console.log("Invalid JSON"); 
         data = {}; 
      }
		
      //switching type of the user message 
      switch (data.type) { 
         //when a user tries to login
         case "login": 
            console.log("User logged", data.name); 
				
            //if anyone is logged in with this username then refuse 
            if(users[data.name]) { 
               sendTo(connection, { 
                  type: "login", 
                  success: false 
               }); 
            } else { 
               //save user connection on the server 
               users[data.name] = connection; 
               connection.name = data.name; 
					
               sendTo(connection, { 
                  type: "login", 
                  success: true 
               }); 
            } 
				
            break;
				
         case "offer": 
            //for ex. UserA wants to call UserB 
            console.log("Sending offer to: ", data.name);
				
            //if UserB exists then send him offer details 
            var conn = users[data.name]; 
				
            if(conn != null) { 
               //setting that UserA connected with UserB 
               connection.otherName = data.name; 
					
               sendTo(conn, { 
                  type: "offer", 
                  offer: data.offer, 
                  name: connection.name 
               }); 
            }
				
            break;
				
         case "answer": 
            console.log("Sending answer to: ", data.name); 
            //for ex. UserB answers UserA 
            var conn = users[data.name]; 
				
            if(conn != null) { 
               connection.otherName = data.name; 
               sendTo(conn, { 
                  type: "answer", 
                  answer: data.answer 
               }); 
            } 
				
            break; 
				
         case "candidate": 
            console.log("Sending candidate to:",data.name); 
            var conn = users[data.name];
				
            if(conn != null) { 
               sendTo(conn, { 
                  type: "candidate", 
                  candidate: data.candidate 
               }); 
            } 
				
            break;
				
         case "leave": 
            console.log("Disconnecting from", data.name); 
            var conn = users[data.name]; 
            conn.otherName = null; 
				
            //notify the other user so he can disconnect his peer connection 
            if(conn != null) {
               sendTo(conn, { 
                  type: "leave" 
              }); 
            }
				
            break;
				
         default: 
            sendTo(connection, { 
               type: "error", 
               message: "Command not found: " + data.type 
            }); 
				
            break; 
      }
		
   }); 
	
   //when user exits, for example closes a browser window 
   //this may help if we are still in "offer","answer" or "candidate" state 
   connection.on("close", function() { 
	
      if(connection.name) { 
         delete users[connection.name]; 
			
         if(connection.otherName) { 
            console.log("Disconnecting from ", connection.otherName); 
            var conn = users[connection.otherName]; 
            conn.otherName = null;
				
            if(conn != null) { 
               sendTo(conn, { 
                  type: "leave" 
               }); 
            }
         } 
      }
		
   });  
	
   connection.send("Hello world");  
});
  
function sendTo(connection, message) { 
   connection.send(JSON.stringify(message)); 
}

Solicitud de cliente

Una forma de probar esta aplicación es abrir dos pestañas del navegador e intentar llamarse entre sí.

En primer lugar, necesitamos instalar la biblioteca de arranque . Bootstrap es un marco frontend para desarrollar aplicaciones web. Puedes aprender más enhttp://getbootstrap.com/.Cree una carpeta llamada, por ejemplo, "videochat". Esta será nuestra carpeta raíz de la aplicación. Dentro de esta carpeta, cree un archivo package.json (es necesario para administrar las dependencias de npm) y agregue lo siguiente:

{ 
   "name": "webrtc-videochat", 
   "version": "0.1.0", 
   "description": "webrtc-videochat", 
   "author": "Author", 
   "license": "BSD-2-Clause" 
}

Luego ejecute npm install bootstrap . Esto instalará la biblioteca bootstrap en la carpeta videochat / node_modules .

Ahora necesitamos crear una página HTML básica. Cree un archivo index.html en la carpeta raíz con el siguiente código:

<html> 
 
   <head> 
      <title>WebRTC Video Demo</title>
      <link rel = "stylesheet" href = "node_modules/bootstrap/dist/css/bootstrap.min.css"/>
   </head>
	
   <style>
	
      body { 
         background: #eee; 
         padding: 5% 0; 
      } 
		
      video { 
         background: black; 
         border: 1px solid gray; 
      }
		
      .call-page { 
         position: relative; 
         display: block; 
         margin: 0 auto; 
         width: 500px; 
         height: 500px; 
      } 
		
      #localVideo { 
         width: 150px; 
         height: 150px; 
         position: absolute; 
         top: 15px; 
         right: 15px; 
      }
		
      #remoteVideo { 
         width: 500px; 
         height: 500px; 
      }
		
   </style>
	
   <body>
	
   <div id = "loginPage" class = "container text-center"> 
	
      <div class = "row"> 
         <div class = "col-md-4 col-md-offset-4">
			
            <h2>WebRTC Video Demo. Please sign in</h2> 
            <label for = "usernameInput" class = "sr-only">Login</label> 
            <input type = "email" id = "usernameInput" c
               lass = "form-control formgroup" placeholder = "Login" 
               required = "" autofocus = ""> 
            <button id = "loginBtn" class = "btn btn-lg btn-primary btnblock">
               Sign in</button>
				
         </div> 
      </div> 
		
   </div>
	
   <div id = "callPage" class = "call-page"> 
      <video id = "localVideo" autoplay></video> 
      <video id = "remoteVideo" autoplay></video>
		
      <div class = "row text-center"> 
         <div class = "col-md-12"> 
            <input id = "callToUsernameInput" type = "text"
               placeholder = "username to call" /> 
            <button id = "callBtn" class = "btn-success btn">Call</button> 
            <button id = "hangUpBtn" class = "btn-danger btn">Hang Up</button> 
         </div>	
      </div> 
		
   </div>
	
   <script src = "client.js"></script> 
	
   </body>
	
</html>

Esta página debería resultarle familiar. Hemos agregado el archivo bootstrap css. También hemos definido dos páginas. Finalmente, hemos creado varios campos de texto y botones para obtener información del usuario. Debería ver los dos elementos de video para transmisiones de video locales y remotas. Observe que hemos agregado un enlace a un archivo client.js .

Ahora necesitamos establecer una conexión con nuestro servidor de señalización. Cree el archivo client.js en la carpeta raíz con el siguiente código:

//our username 
var name; 
var connectedUser;
  
//connecting to our signaling server 
var conn = new WebSocket('ws://localhost:9090');
  
conn.onopen = function () { 
   console.log("Connected to the signaling server"); 
};
  
//when we got a message from a signaling server 
conn.onmessage = function (msg) { 
   console.log("Got message", msg.data);
	
   var data = JSON.parse(msg.data);
	
   switch(data.type) { 
      case "login": 
         handleLogin(data.success);
         break; 
      //when somebody wants to call us 
      case "offer": 
         handleOffer(data.offer, data.name); 
         break; 
      case "answer": 
         handleAnswer(data.answer); 
         break; 
      //when a remote peer sends an ice candidate to us 
      case "candidate": 
         handleCandidate(data.candidate); 
         break; 
      case "leave": 
         handleLeave(); 
         break; 
      default: 
         break; 
   } 
};
  
conn.onerror = function (err) { 
   console.log("Got error", err); 
};
  
//alias for sending JSON encoded messages 
function send(message) { 
   //attach the other peer username to our messages 
   if (connectedUser) { 
      message.name = connectedUser; 
   } 
	
   conn.send(JSON.stringify(message)); 
};

Ahora ejecute nuestro servidor de señalización a través del servidor de nodo . Luego, dentro de la carpeta raíz, ejecute el comando estático y abra la página dentro del navegador. Debería ver la siguiente salida de la consola:

El siguiente paso es implementar un inicio de sesión de usuario con un nombre de usuario único. Simplemente enviamos un nombre de usuario al servidor, que luego nos dice si está tomado o no. Agregue el siguiente código a su archivo client.js :

//****** 
//UI selectors block 
//****** 

var loginPage = document.querySelector('#loginPage'); 
var usernameInput = document.querySelector('#usernameInput'); 
var loginBtn = document.querySelector('#loginBtn'); 

var callPage = document.querySelector('#callPage'); 
var callToUsernameInput = document.querySelector('#callToUsernameInput');
var callBtn = document.querySelector('#callBtn'); 

var hangUpBtn = document.querySelector('#hangUpBtn');
  
//hide call page 
callPage.style.display = "none"; 
 
// Login when the user clicks the button 
loginBtn.addEventListener("click", function (event) { 
   name = usernameInput.value; 
	
   if (name.length > 0) { 
      send({ 
         type: "login", 
         name: name 
      }); 
   } 
	
});
 
function handleLogin(success) { 

   if (success === false) { 
      alert("Ooops...try a different username"); 
   } else { 
      //display the call page if login is successful 
      loginPage.style.display = "none"; 
      callPage.style.display = "block";  
      //start peer connection 
   } 
};

En primer lugar, seleccionamos algunas referencias a los elementos de la página. Ocultamos la página de llamadas. Luego, agregamos un detector de eventos en el botón de inicio de sesión. Cuando el usuario hace clic en él, enviamos su nombre de usuario al servidor. Finalmente, implementamos la devolución de llamada handleLogin. Si el inicio de sesión fue exitoso, mostramos la página de llamada y comenzamos a configurar una conexión entre pares.

Para iniciar una conexión entre pares, necesitamos:

  • Obtenga una transmisión de la cámara web.
  • Cree el objeto RTCPeerConnection.

Agregue el siguiente código al "bloque de selectores de UI" -

var localVideo = document.querySelector('#localVideo'); 
var remoteVideo = document.querySelector('#remoteVideo');
 
var yourConn; 
var stream;

Modificar la función handleLogin -

function handleLogin(success) { 

   if (success === false) { 
      alert("Ooops...try a different username"); 
   } else { 
      loginPage.style.display = "none"; 
      callPage.style.display = "block";
		  
      //********************** 
      //Starting a peer connection 
      //********************** 
		
      //getting local video stream 
      navigator.webkitGetUserMedia({ video: true, audio: true }, function (myStream) { 
         stream = myStream; 
			
         //displaying local video stream on the page 
         localVideo.src = window.URL.createObjectURL(stream);
			
         //using Google public stun server 
         var configuration = { 
            "iceServers": [{ "url": "stun:stun2.1.google.com:19302" }] 
         }; 
			
         yourConn = new webkitRTCPeerConnection(configuration);
			
         // setup stream listening 
         yourConn.addStream(stream); 
			
         //when a remote user adds stream to the peer connection, we display it 
         yourConn.onaddstream = function (e) { 
            remoteVideo.src = window.URL.createObjectURL(e.stream); 
         };
			
         // Setup ice handling 
         yourConn.onicecandidate = function (event) {
			
            if (event.candidate) { 
               send({ 
                  type: "candidate", 
                  candidate: event.candidate 
               }); 
            } 
				
         };
			
      }, function (error) { 
         console.log(error); 
      }); 
   } 
};

Ahora, si ejecuta el código, la página debería permitirle iniciar sesión y mostrar su transmisión de video local en la página.

Ahora estamos listos para iniciar una llamada. En primer lugar, enviamos una oferta a otro usuario. Una vez que un usuario recibe la oferta, crea una respuesta y comienza a intercambiar candidatos de ICE. Agregue el siguiente código al archivo client.js :

//initiating a call 
callBtn.addEventListener("click", function () { 
   var callToUsername = callToUsernameInput.value; 
	
   if (callToUsername.length > 0) {
	
      connectedUser = callToUsername;
		
      // create an offer
      yourConn.createOffer(function (offer) { 
         send({ 
            type: "offer", 
            offer: offer 
         }); 
			
         yourConn.setLocalDescription(offer); 
			
      }, function (error) { 
         alert("Error when creating an offer"); 
      });  
   } 
});
  
//when somebody sends us an offer 
function handleOffer(offer, name) { 
   connectedUser = name; 
   yourConn.setRemoteDescription(new RTCSessionDescription(offer));
	
   //create an answer to an offer 
   yourConn.createAnswer(function (answer) { 
      yourConn.setLocalDescription(answer); 
		
      send({ 
         type: "answer", 
         answer: answer 
      }); 
		
   }, function (error) { 
      alert("Error when creating an answer"); 
   }); 
};
  
//when we got an answer from a remote user 
function handleAnswer(answer) { 
   yourConn.setRemoteDescription(new RTCSessionDescription(answer));
}; 
 
//when we got an ice candidate from a remote user 
function handleCandidate(candidate) { 
   yourConn.addIceCandidate(new RTCIceCandidate(candidate)); 
};

Agregamos un controlador de clic al botón Llamar, que inicia una oferta. Luego implementamos varios controladores esperados por el controlador onmessage . Se procesarán de forma asincrónica hasta que ambos usuarios se hayan conectado.

El último paso es implementar la función de colgar. Esto dejará de transmitir datos y le dirá al otro usuario que cierre la llamada. Agregue el siguiente código:

//hang up 
hangUpBtn.addEventListener("click", function () { 

   send({ 
      type: "leave" 
   });
	
   handleLeave(); 
});
  
function handleLeave() { 
   connectedUser = null; 
   remoteVideo.src = null; 
	
   yourConn.close(); 
   yourConn.onicecandidate = null; 
   yourConn.onaddstream = null; 
};

Cuando el usuario hace clic en el botón Colgar:

  • Enviará un mensaje de "dejar" al otro usuario
  • Cerrará RTCPeerConnection y destruirá la conexión localmente

Ahora ejecuta el código. Debería poder iniciar sesión en el servidor utilizando dos pestañas del navegador. Luego puede llamar a la pestaña y colgar la llamada.

El siguiente es el archivo client.js completo :

//our username 
var name; 
var connectedUser;
  
//connecting to our signaling server
var conn = new WebSocket('ws://localhost:9090');
  
conn.onopen = function () { 
   console.log("Connected to the signaling server"); 
};
  
//when we got a message from a signaling server 
conn.onmessage = function (msg) { 
   console.log("Got message", msg.data);
	
   var data = JSON.parse(msg.data); 
	
   switch(data.type) { 
      case "login": 
         handleLogin(data.success); 
         break; 
      //when somebody wants to call us 
      case "offer": 
         handleOffer(data.offer, data.name); 
         break; 
      case "answer": 
         handleAnswer(data.answer); 
         break; 
      //when a remote peer sends an ice candidate to us 
      case "candidate": 
         handleCandidate(data.candidate); 
         break; 
      case "leave": 
         handleLeave(); 
         break; 
      default: 
         break; 
   }
};
  
conn.onerror = function (err) { 
   console.log("Got error", err); 
};
  
//alias for sending JSON encoded messages 
function send(message) { 
   //attach the other peer username to our messages 
   if (connectedUser) { 
      message.name = connectedUser; 
   } 
	
   conn.send(JSON.stringify(message)); 
};
  
//****** 
//UI selectors block 
//******
 
var loginPage = document.querySelector('#loginPage'); 
var usernameInput = document.querySelector('#usernameInput'); 
var loginBtn = document.querySelector('#loginBtn'); 

var callPage = document.querySelector('#callPage'); 
var callToUsernameInput = document.querySelector('#callToUsernameInput');
var callBtn = document.querySelector('#callBtn'); 

var hangUpBtn = document.querySelector('#hangUpBtn');
  
var localVideo = document.querySelector('#localVideo'); 
var remoteVideo = document.querySelector('#remoteVideo'); 

var yourConn; 
var stream;
  
callPage.style.display = "none";

// Login when the user clicks the button 
loginBtn.addEventListener("click", function (event) { 
   name = usernameInput.value;
	
   if (name.length > 0) { 
      send({ 
         type: "login", 
         name: name 
      }); 
   }
	
});
  
function handleLogin(success) { 
   if (success === false) { 
      alert("Ooops...try a different username"); 
   } else { 
      loginPage.style.display = "none"; 
      callPage.style.display = "block";
		
      //********************** 
      //Starting a peer connection 
      //********************** 
		
      //getting local video stream 
      navigator.webkitGetUserMedia({ video: true, audio: true }, function (myStream) { 
         stream = myStream; 
			
         //displaying local video stream on the page 
         localVideo.src = window.URL.createObjectURL(stream);
			
         //using Google public stun server 
         var configuration = { 
            "iceServers": [{ "url": "stun:stun2.1.google.com:19302" }]
         }; 
			
         yourConn = new webkitRTCPeerConnection(configuration); 
			
         // setup stream listening 
         yourConn.addStream(stream); 
			
         //when a remote user adds stream to the peer connection, we display it 
         yourConn.onaddstream = function (e) { 
            remoteVideo.src = window.URL.createObjectURL(e.stream); 
         };
			
         // Setup ice handling 
         yourConn.onicecandidate = function (event) { 
            if (event.candidate) { 
               send({ 
                  type: "candidate", 
                  candidate: event.candidate 
               }); 
            } 
         };  
			
      }, function (error) { 
         console.log(error); 
      }); 
		
   } 
};
  
//initiating a call 
callBtn.addEventListener("click", function () { 
   var callToUsername = callToUsernameInput.value;
	
   if (callToUsername.length > 0) { 
	
      connectedUser = callToUsername;
		
      // create an offer 
      yourConn.createOffer(function (offer) { 
         send({ 
            type: "offer", 
            offer: offer 
         }); 
			
         yourConn.setLocalDescription(offer); 
      }, function (error) { 
         alert("Error when creating an offer"); 
      });
		
   } 
});
  
//when somebody sends us an offer 
function handleOffer(offer, name) { 
   connectedUser = name; 
   yourConn.setRemoteDescription(new RTCSessionDescription(offer));
	
   //create an answer to an offer 
   yourConn.createAnswer(function (answer) { 
      yourConn.setLocalDescription(answer); 
		
      send({ 
         type: "answer", 
         answer: answer 
      }); 
		
   }, function (error) { 
      alert("Error when creating an answer"); 
   }); 
};
  
//when we got an answer from a remote user
function handleAnswer(answer) { 
   yourConn.setRemoteDescription(new RTCSessionDescription(answer)); 
};
  
//when we got an ice candidate from a remote user 
function handleCandidate(candidate) { 
   yourConn.addIceCandidate(new RTCIceCandidate(candidate)); 
};
   
//hang up 
hangUpBtn.addEventListener("click", function () { 

   send({ 
      type: "leave" 
   });  
	
   handleLeave(); 
});
  
function handleLeave() { 
   connectedUser = null; 
   remoteVideo.src = null; 
	
   yourConn.close(); 
   yourConn.onicecandidate = null; 
   yourConn.onaddstream = null; 
};

Resumen

Esta demostración proporciona una línea de base de las características que necesita toda aplicación WebRTC. Para mejorar esta demostración, puede agregar identificación de usuario a través de plataformas como Facebook o Google, manejar la entrada del usuario para datos no válidos. Además, la conexión WebRTC puede fallar debido a varias razones, como no admitir la tecnología o no poder atravesar los firewalls. Se ha invertido mucho trabajo en hacer que cualquier aplicación WebRTC sea estable.

En este capítulo, vamos a crear una aplicación cliente que permite que dos usuarios en dispositivos separados se comuniquen usando transmisiones de audio WebRTC. Nuestra aplicación tendrá dos páginas. Uno para iniciar sesión y el otro para realizar una llamada de audio a otro usuario.

Las dos páginas serán las etiquetas div . La mayor parte de la entrada se realiza a través de controladores de eventos simples.

Servidor de señalización

Para crear una conexión WebRTC, los clientes deben poder transferir mensajes sin utilizar una conexión de pares WebRTC. Aquí es donde usaremos HTML5 WebSockets, una conexión de socket bidireccional entre dos puntos finales, un servidor web y un navegador web. Ahora comencemos a usar la biblioteca WebSocket. Cree el archivo server.js e inserte el siguiente código:

//require our websocket library 
var WebSocketServer = require('ws').Server; 

//creating a websocket server at port 9090 
var wss = new WebSocketServer({port: 9090});
  
//when a user connects to our sever 
wss.on('connection', function(connection) { 
   console.log("user connected"); 
	
   //when server gets a message from a connected user 
   connection.on('message', function(message) { 
      console.log("Got message from a user:", message); 
   }); 
	
   connection.send("Hello from server"); 
});

La primera línea requiere la biblioteca WebSocket que ya hemos instalado. Luego creamos un servidor de socket en el puerto 9090. Luego, escuchamos el evento de conexión . Este código se ejecutará cuando un usuario establezca una conexión WebSocket al servidor. Luego escuchamos los mensajes enviados por el usuario. Finalmente, enviamos una respuesta al usuario conectado diciendo "Hola desde el servidor".

En nuestro servidor de señalización, usaremos un nombre de usuario basado en cadenas para cada conexión para que sepamos dónde enviar mensajes. Cambiemos un poco nuestro controlador de conexión :

connection.on('message', function(message) { 
   var data; 
	
   //accepting only JSON messages 
   try { 
      data = JSON.parse(message); 
   } catch (e) { 
      console.log("Invalid JSON");
      data = {}; 
   } 
});

De esta forma solo aceptamos mensajes JSON. A continuación, necesitamos almacenar a todos los usuarios conectados en algún lugar. Usaremos un objeto Javascript simple para ello. Cambie la parte superior de nuestro archivo -

//require our websocket library 
var WebSocketServer = require('ws').Server; 

//creating a websocket server at port 9090 
var wss = new WebSocketServer({port: 9090}); 

//all connected to the server users 
var users = {};

Vamos a agregar un campo de tipo para cada mensaje proveniente del cliente. Por ejemplo, si un usuario desea iniciar sesión, envía el mensaje de tipo de inicio de sesión . Vamos a definirlo -

connection.on('message', function(message) {
  
   var data; 
   //accepting only JSON messages 
   try { 
      data = JSON.parse(message); 
   } catch (e) { 
      console.log("Invalid JSON"); 
      data = {}; 
   } 
	
   //switching type of the user message 
   switch (data.type) { 
      //when a user tries to login 
      case "login": 
         console.log("User logged:", data.name); 
			
         //if anyone is logged in with this username then refuse 
         if(users[data.name]) { 
            sendTo(connection, { 
               type: "login",
               success: false 
            }); 
         } else { 
            //save user connection on the server 
            users[data.name] = connection; 
            connection.name = data.name; 
				
            sendTo(connection, { 
               type: "login", 
               success: true 
            }); 
         } 
			
         break;
			
      default: 
         sendTo(connection, { 
            type: "error", 
            message: "Command no found: " + data.type 
         }); 
			
         break; 
   }  
});

Si el usuario envía un mensaje con el tipo de inicio de sesión , nosotros:

  • Compruebe si alguien ya ha iniciado sesión con este nombre de usuario.
  • Si es así, dígale al usuario que no ha iniciado sesión correctamente.
  • Si nadie está usando este nombre de usuario, agregamos el nombre de usuario como clave para el objeto de conexión.
  • Si no se reconoce un comando, enviamos un error.

El siguiente código es una función auxiliar para enviar mensajes a una conexión. Añadirlo a la server.js archivo -

function sendTo(connection, message) { 
   connection.send(JSON.stringify(message)); 
}

Cuando el usuario se desconecta debemos limpiar su conexión. Podemos eliminar al usuario cuando se dispara el evento de cierre . Agregue el siguiente código al controlador de conexión

connection.on("close", function() { 
   if(connection.name) { 
      delete users[connection.name]; 
   } 
});

Después de iniciar sesión correctamente, el usuario desea llamar a otro. Debería hacer una oferta a otro usuario para lograrlo. Agregar el controlador de ofertas -

case "offer": 
   //for ex. UserA wants to call UserB 
   console.log("Sending offer to: ", data.name); 
	
   //if UserB exists then send him offer details 
   var conn = users[data.name]; 
	
   if(conn != null) { 
      //setting that UserA connected with UserB 
      connection.otherName = data.name; 
      sendTo(conn, { 
         type: "offer", 
         offer: data.offer, 
         name: connection.name 
      });
   }		
	
   break;

En primer lugar, obtenemos la conexión del usuario al que intentamos llamar. Si existe le enviamos los detalles de la oferta . También agregamos otherName al objeto de conexión . Esto se hace por la simplicidad de encontrarlo más tarde.

Responder a la respuesta tiene un patrón similar al que usamos en el controlador de ofertas . Nuestro servidor simplemente pasa todos los mensajes como respuesta a otro usuario. Agregue el siguiente código después del controlador de ofertas :

case "answer": 
   console.log("Sending answer to: ", data.name); 
   //for ex. UserB answers UserA
   var conn = users[data.name]; 
	
   if(conn != null) { 
      connection.otherName = data.name;
		
      sendTo(conn, { 
         type: "answer", 
         answer: data.answer 
      }); 
   } 
	
   break;

La parte final es el manejo del candidato ICE entre usuarios. Usamos la misma técnica simplemente pasando mensajes entre usuarios. La principal diferencia es que los mensajes candidatos pueden aparecer varias veces por usuario en cualquier orden. Agregar el controlador de candidatos -

case "candidate": 
   console.log("Sending candidate to:",data.name); 
   var conn = users[data.name];
	
   if(conn != null) { 
      sendTo(conn, { 
         type: "candidate", 
         candidate: data.candidate 
      }); 
   } 
	
   break;

Para permitir que nuestros usuarios se desconecten de otro usuario, debemos implementar la función de colgar. También le indicará al servidor que elimine todas las referencias de usuario. Agregue el controlador de licencia -

case "leave": 
   console.log("Disconnecting from", data.name); 
   var conn = users[data.name]; 
   conn.otherName = null; 
	
   //notify the other user so he can disconnect his peer connection 
   if(conn != null) { 
      sendTo(conn, {
         type: "leave" 
      }); 
   }  
	
   break;

Esto también enviará al otro usuario el evento de licencia para que pueda desconectar su conexión de igual en consecuencia. También deberíamos manejar el caso cuando un usuario pierde su conexión del servidor de señalización. Modifiquemos nuestro controlador cercano -

connection.on("close", function() { 

   if(connection.name) { 
      delete users[connection.name]; 
		
      if(connection.otherName) { 
         console.log("Disconnecting from ", connection.otherName); 
         var conn = users[connection.otherName]; 
         conn.otherName = null;
			
         if(conn != null) { 
            sendTo(conn, { 
               type: "leave" 
            }); 
         }
			
      } 
   } 
});

El siguiente es el código completo de nuestro servidor de señalización:

//require our websocket library 
var WebSocketServer = require('ws').Server; 

//creating a websocket server at port 9090 
var wss = new WebSocketServer({port: 9090}); 

//all connected to the server users 
var users = {};

//when a user connects to our sever 
wss.on('connection', function(connection) {
  
   console.log("User connected");
	
   //when server gets a message from a connected user 
   connection.on('message', function(message) { 
	
      var data;
		
      //accepting only JSON messages 
      try { 
         data = JSON.parse(message); 
      } catch (e) { 
         console.log("Invalid JSON"); 
         data = {}; 
      }
		
      //switching type of the user message 
      switch (data.type) { 
         //when a user tries to login 
         case "login": 
            console.log("User logged", data.name); 
				
            //if anyone is logged in with this username then refuse 
            if(users[data.name]) { 
               sendTo(connection, { 
                  type: "login", 
                  success: false 
               }); 
            } else { 
               //save user connection on the server 
               users[data.name] = connection; 
               connection.name = data.name;
               sendTo(connection, { 
                  type: "login", 
                  success: true 
               }); 
            } 
				
            break;
				
         case "offer": 
            //for ex. UserA wants to call UserB 
            console.log("Sending offer to: ", data.name); 
				
            //if UserB exists then send him offer details 
            var conn = users[data.name]; 
				
            if(conn != null) { 
               //setting that UserA connected with UserB 
               connection.otherName = data.name; 
               sendTo(conn, { 
                  type: "offer", 
                  offer: data.offer, 
                  name: connection.name 
               }); 
            } 
				
            break;
				
         case "answer": 
            console.log("Sending answer to: ", data.name); 
            //for ex. UserB answers UserA 
            var conn = users[data.name]; 
				
            if(conn != null) { 
               connection.otherName = data.name; 
               sendTo(conn, { 
                  type: "answer", 
                  answer: data.answer 
               });
            } 
				
            break;
				
         case "candidate": 
            console.log("Sending candidate to:",data.name); 
            var conn = users[data.name];  
				
            if(conn != null) { 
               sendTo(conn, { 
                  type: "candidate", 
                  candidate: data.candidate 
               }); 
            } 
				
            break;
				
         case "leave": 
            console.log("Disconnecting from", data.name); 
            var conn = users[data.name]; 
            conn.otherName = null; 
				
            //notify the other user so he can disconnect his peer connection 
            if(conn != null) { 
               sendTo(conn, { 
                  type: "leave" 
               }); 
            }  
				
            break;
				
         default: 
            sendTo(connection, { 
               type: "error", 
               message: "Command not found: " + data.type 
            });
				
            break; 
      }  
   });
	
   //when user exits, for example closes a browser window 
   //this may help if we are still in "offer","answer" or "candidate" state 
   connection.on("close", function() { 
	
      if(connection.name) { 
         delete users[connection.name]; 
			
         if(connection.otherName) { 
            console.log("Disconnecting from ", connection.otherName); 
            var conn = users[connection.otherName]; 
            conn.otherName = null;  
				
            if(conn != null) { 
               sendTo(conn, { 
                  type: "leave" 
              }); 
            }  
         } 
      } 
   });  
	
   connection.send("Hello world"); 
}); 
 
function sendTo(connection, message) { 
   connection.send(JSON.stringify(message)); 
}

Solicitud de cliente

Una forma de probar esta aplicación es abrir dos pestañas del navegador e intentar realizar una llamada de audio entre sí.

En primer lugar, necesitamos instalar la biblioteca de arranque . Bootstrap es un marco frontend para desarrollar aplicaciones web. Puedes aprender más enhttp://getbootstrap.com/.Cree una carpeta llamada, por ejemplo, "audiochat". Esta será nuestra carpeta raíz de la aplicación. Dentro de esta carpeta, cree un archivo package.json (es necesario para administrar las dependencias de npm) y agregue lo siguiente:

{ 
   "name": "webrtc-audiochat", 
   "version": "0.1.0", 
   "description": "webrtc-audiochat", 
   "author": "Author", 
   "license": "BSD-2-Clause" 
}

Luego ejecute npm install bootstrap . Esto instalará la biblioteca bootstrap en la carpeta audiochat / node_modules .

Ahora necesitamos crear una página HTML básica. Cree un archivo index.html en la carpeta raíz con el siguiente código:

<html>
 
   <head> 
      <title>WebRTC Voice Demo</title> 
      <link rel = "stylesheet" href = "node_modules/bootstrap/dist/css/bootstrap.min.css"/> 
   </head>
 
   <style> 
      body { 
         background: #eee; 
         padding: 5% 0; 
      } 
   </style>
	
   <body> 
      <div id = "loginPage" class = "container text-center"> 
		
         <div class = "row"> 
            <div class = "col-md-4 col-md-offset-4">
				
               <h2>WebRTC Voice Demo. Please sign in</h2>
				
               <label for = "usernameInput" class = "sr-only">Login</label> 
               <input type = "email" id = "usernameInput" 
                  class = "form-control formgroup"
                  placeholder = "Login" required = "" autofocus = ""> 
               <button id = "loginBtn" class = "btn btn-lg btn-primary btnblock">
                  Sign in</button> 
            </div> 
         </div> 
			
      </div>
		
      <div id = "callPage" class = "call-page">
		
         <div class = "row"> 
			
            <div class = "col-md-6 text-right"> 
               Local audio: <audio id = "localAudio" 
               controls autoplay></audio> 
            </div>
				
            <div class = "col-md-6 text-left"> 
               Remote audio: <audio id = "remoteAudio" 
                  controls autoplay></audio> 
            </div> 
				
         </div> 
			
         <div class = "row text-center"> 
            <div class = "col-md-12"> 
               <input id = "callToUsernameInput" 
                  type = "text" placeholder = "username to call" /> 
               <button id = "callBtn" class = "btn-success btn">Call</button> 
               <button id = "hangUpBtn" class = "btn-danger btn">Hang Up</button> 
            </div> 
         </div>
			
      </div> 
		
      <script src = "client.js"></script> 
		
   </body>
	
</html>

Esta página debería resultarle familiar. Hemos agregado el archivo bootstrap css. También hemos definido dos páginas. Finalmente, hemos creado varios campos de texto y botones para obtener información del usuario. Debería ver los dos elementos de audio para transmisiones de audio locales y remotas. Observe que hemos agregado un enlace a un archivo client.js .

Ahora necesitamos establecer una conexión con nuestro servidor de señalización. Cree el archivo client.js en la carpeta raíz con el siguiente código:

//our username 
var name; 
var connectedUser;
  
//connecting to our signaling server 
var conn = new WebSocket('ws://localhost:9090');
  
conn.onopen = function () { 
   console.log("Connected to the signaling server"); 
}; 
 
//when we got a message from a signaling server 
conn.onmessage = function (msg) { 
   console.log("Got message", msg.data);  
   var data = JSON.parse(msg.data);  
	
   switch(data.type) { 
      case "login": 
         handleLogin(data.success); 
         break; 
      //when somebody wants to call us 
      case "offer": 
         handleOffer(data.offer, data.name); 
         break; 
      case "answer": 
         handleAnswer(data.answer); 
         break; 
      //when a remote peer sends an ice candidate to us 
      case "candidate": 
         handleCandidate(data.candidate); 
         break;
      case "leave": 
         handleLeave(); 
         break; 
      default: 
         break; 
   } 
};
  
conn.onerror = function (err) { 
   console.log("Got error", err); 
};
  
//alias for sending JSON encoded messages 
function send(message) { 
   //attach the other peer username to our messages
   if (connectedUser) { 
      message.name = connectedUser; 
   } 
	
   conn.send(JSON.stringify(message)); 
};

Ahora ejecute nuestro servidor de señalización a través del servidor de nodo . Luego, dentro de la carpeta raíz, ejecute el comando estático y abra la página dentro del navegador. Debería ver la siguiente salida de la consola:

El siguiente paso es implementar un inicio de sesión de usuario con un nombre de usuario único. Simplemente enviamos un nombre de usuario al servidor, que luego nos dice si está tomado o no. Agregue el siguiente código a su archivo client.js :

//****** 
//UI selectors block 
//******

var loginPage = document.querySelector('#loginPage'); 
var usernameInput = document.querySelector('#usernameInput'); 
var loginBtn = document.querySelector('#loginBtn');
 
var callPage = document.querySelector('#callPage'); 
var callToUsernameInput = document.querySelector('#callToUsernameInput');
var callBtn = document.querySelector('#callBtn');
 
var hangUpBtn = document.querySelector('#hangUpBtn');
  
callPage.style.display = "none";
  
// Login when the user clicks the button 
loginBtn.addEventListener("click", function (event) { 
   name = usernameInput.value;
	
   if (name.length > 0) { 
      send({
         type: "login", 
         name: name 
      }); 
   } 
	
}); 
 
function handleLogin(success) { 
   if (success === false) { 
      alert("Ooops...try a different username"); 
   } else { 
      loginPage.style.display = "none"; 
      callPage.style.display = "block"; 
		
      //********************** 
      //Starting a peer connection 
      //**********************
		         
   } 
	
};

En primer lugar, seleccionamos algunas referencias a los elementos de la página. Ocultamos la página de llamadas. Luego, agregamos un detector de eventos en el botón de inicio de sesión. Cuando el usuario hace clic en él, enviamos su nombre de usuario al servidor. Finalmente, implementamos la devolución de llamada handleLogin. Si el inicio de sesión fue exitoso, mostramos la página de llamada y comenzamos a configurar una conexión entre pares.

Para iniciar una conexión entre pares, necesitamos:

  • Obtenga una transmisión de audio de un micrófono
  • Cree el objeto RTCPeerConnection

Agregue el siguiente código al "bloque de selectores de UI" -

var localAudio = document.querySelector('#localAudio'); 
var remoteAudio = document.querySelector('#remoteAudio'); 

var yourConn; 
var stream;

Modificar la función handleLogin -

function handleLogin(success) { 
   if (success === false) { 
      alert("Ooops...try a different username"); 
   } else { 
      loginPage.style.display = "none"; 
      callPage.style.display = "block";
		
      //********************** 
      //Starting a peer connection 
      //********************** 
		
      //getting local audio stream 
      navigator.webkitGetUserMedia({ video: false, audio: true }, function (myStream) { 
         stream = myStream; 
			
         //displaying local audio stream on the page
         localAudio.src = window.URL.createObjectURL(stream);
			
         //using Google public stun server 
         var configuration = { 
            "iceServers": [{ "url": "stun:stun2.1.google.com:19302" }] 
         }; 
			
         yourConn = new webkitRTCPeerConnection(configuration); 
			
         // setup stream listening 
         yourConn.addStream(stream); 
			
         //when a remote user adds stream to the peer connection, we display it 
         yourConn.onaddstream = function (e) { 
            remoteAudio.src = window.URL.createObjectURL(e.stream); 
         }; 
			
         // Setup ice handling 
         yourConn.onicecandidate = function (event) { 
            if (event.candidate) { 
               send({ 
                  type: "candidate", 
               }); 
            } 
         };  
			
      }, function (error) { 
         console.log(error); 
      }); 
		
   } 
};

Ahora, si ejecuta el código, la página debería permitirle iniciar sesión y mostrar su transmisión de audio local en la página.

Ahora estamos listos para iniciar una llamada. En primer lugar, enviamos una oferta a otro usuario. Una vez que un usuario recibe la oferta, crea una respuesta y comienza a intercambiar candidatos de ICE. Agregue el siguiente código al archivo client.js :

//initiating a call 
callBtn.addEventListener("click", function () { 
   var callToUsername = callToUsernameInput.value; 
	
   if (callToUsername.length > 0) { 
      connectedUser = callToUsername; 
		
      // create an offer 
      yourConn.createOffer(function (offer) { 
         send({ 
            type: "offer", 
            offer: offer 
         }); 
			
         yourConn.setLocalDescription(offer); 
			
      }, function (error) { 
         alert("Error when creating an offer"); 
      }); 
   } 
	
});
 
//when somebody sends us an offer 
function handleOffer(offer, name) { 
   connectedUser = name; 
   yourConn.setRemoteDescription(new RTCSessionDescription(offer)); 
	
   //create an answer to an offer 
   yourConn.createAnswer(function (answer) { 
      yourConn.setLocalDescription(answer); 
		
      send({ 
         type: "answer",
         answer: answer 
      }); 
		
   }, function (error) { 
      alert("Error when creating an answer"); 
   }); 
	
};
 
//when we got an answer from a remote user 
function handleAnswer(answer) { 
   yourConn.setRemoteDescription(new RTCSessionDescription(answer)); 
};
 
//when we got an ice candidate from a remote user 
function handleCandidate(candidate) { 
   yourConn.addIceCandidate(new RTCIceCandidate(candidate)); 
};

Agregamos un controlador de clic al botón Llamar, que inicia una oferta. Luego implementamos varios controladores esperados por el controlador onmessage . Se procesarán de forma asincrónica hasta que ambos usuarios se hayan conectado.

El último paso es implementar la función de colgar. Esto dejará de transmitir datos y le dirá al otro usuario que cierre la llamada. Agregue el siguiente código:

//hang up 
hangUpBtn.addEventListener("click", function () { 
   send({ 
      type: "leave" 
   });  
	
   handleLeave(); 
});
  
function handleLeave() { 
   connectedUser = null; 
   remoteAudio.src = null;
	
   yourConn.close(); 
   yourConn.onicecandidate = null; 
   yourConn.onaddstream = null;
};

Cuando el usuario hace clic en el botón Colgar:

  • Enviará un mensaje de "dejar" al otro usuario
  • Cerrará RTCPeerConnection y destruirá la conexión localmente

Ahora ejecuta el código. Debería poder iniciar sesión en el servidor utilizando dos pestañas del navegador. A continuación, puede realizar una llamada de audio a la pestaña y colgar la llamada.

El siguiente es el archivo client.js completo :

//our username 
var name; 
var connectedUser;
 
//connecting to our signaling server 
var conn = new WebSocket('ws://localhost:9090');
 
conn.onopen = function () { 
   console.log("Connected to the signaling server"); 
};
 
//when we got a message from a signaling server 
conn.onmessage = function (msg) { 
   console.log("Got message", msg.data); 
   var data = JSON.parse(msg.data); 
	
   switch(data.type) { 
      case "login": 
         handleLogin(data.success); 
         break; 
      //when somebody wants to call us 
      case "offer": 
         handleOffer(data.offer, data.name); 
         break; 
      case "answer": 
         handleAnswer(data.answer); 
         break; 
      //when a remote peer sends an ice candidate to us 
      case "candidate": 
         handleCandidate(data.candidate); 
         break; 
      case "leave": 
         handleLeave(); 
         break; 
      default: 
         break; 
   } 
}; 

conn.onerror = function (err) { 
   console.log("Got error", err); 
};
 
//alias for sending JSON encoded messages 
function send(message) { 
   //attach the other peer username to our messages 
   if (connectedUser) { 
      message.name = connectedUser; 
   } 
	
   conn.send(JSON.stringify(message)); 
};
 
//****** 
//UI selectors block 
//****** 

var loginPage = document.querySelector('#loginPage'); 
var usernameInput = document.querySelector('#usernameInput'); 
var loginBtn = document.querySelector('#loginBtn');

var callPage = document.querySelector('#callPage'); 
var callToUsernameInput = document.querySelector('#callToUsernameInput');
var callBtn = document.querySelector('#callBtn'); 

var hangUpBtn = document.querySelector('#hangUpBtn'); 
var localAudio = document.querySelector('#localAudio'); 
var remoteAudio = document.querySelector('#remoteAudio'); 

var yourConn; 
var stream; 

callPage.style.display = "none";
 
// Login when the user clicks the button 
loginBtn.addEventListener("click", function (event) { 
   name = usernameInput.value; 
	
   if (name.length > 0) { 
      send({ 
         type: "login", 
         name: name 
      }); 
   } 
	
});
 
function handleLogin(success) { 
   if (success === false) { 
      alert("Ooops...try a different username"); 
   } else { 
      loginPage.style.display = "none"; 
      callPage.style.display = "block"; 
		
      //********************** 
      //Starting a peer connection 
      //********************** 
		
      //getting local audio stream 
      navigator.webkitGetUserMedia({ video: false, audio: true }, function (myStream) { 
         stream = myStream; 
			
         //displaying local audio stream on the page 
         localAudio.src = window.URL.createObjectURL(stream);
			
         //using Google public stun server 
         var configuration = { 
            "iceServers": [{ "url": "stun:stun2.1.google.com:19302" }] 
         }; 
			
         yourConn = new webkitRTCPeerConnection(configuration); 
			
         // setup stream listening 
         yourConn.addStream(stream); 
			
         //when a remote user adds stream to the peer connection, we display it 
         yourConn.onaddstream = function (e) { 
            remoteAudio.src = window.URL.createObjectURL(e.stream); 
         }; 
			
         // Setup ice handling 
         yourConn.onicecandidate = function (event) { 
            if (event.candidate) { 
               send({ 
                  type: "candidate", 
                  candidate: event.candidate 
               }); 
            } 
         }; 
			
      }, function (error) { 
         console.log(error); 
      }); 
		
   } 
};
 
//initiating a call 
callBtn.addEventListener("click", function () { 
   var callToUsername = callToUsernameInput.value; 
	
   if (callToUsername.length > 0) { 
      connectedUser = callToUsername; 
		
      // create an offer 
      yourConn.createOffer(function (offer) { 
         send({
            type: "offer", 
            offer: offer 
         }); 
			
         yourConn.setLocalDescription(offer); 
      }, function (error) { 
         alert("Error when creating an offer"); 
      }); 
   } 
});
 
//when somebody sends us an offer 
function handleOffer(offer, name) { 
   connectedUser = name; 
   yourConn.setRemoteDescription(new RTCSessionDescription(offer)); 
	
   //create an answer to an offer 
   yourConn.createAnswer(function (answer) { 
      yourConn.setLocalDescription(answer); 
		
      send({ 
         type: "answer", 
         answer: answer 
      });
		
   }, function (error) { 
      alert("Error when creating an answer"); 
   }); 
	
};
 
//when we got an answer from a remote user 
function handleAnswer(answer) { 
   yourConn.setRemoteDescription(new RTCSessionDescription(answer)); 
};
 
//when we got an ice candidate from a remote user 
function handleCandidate(candidate) { 
   yourConn.addIceCandidate(new RTCIceCandidate(candidate)); 
};
 
//hang up
hangUpBtn.addEventListener("click", function () { 
   send({ 
      type: "leave" 
   }); 
	
   handleLeave(); 
});
 
function handleLeave() { 
   connectedUser = null; 
   remoteAudio.src = null; 
	
   yourConn.close(); 
   yourConn.onicecandidate = null; 
   yourConn.onaddstream = null; 
};

En este capítulo, vamos a crear una aplicación cliente que permita a dos usuarios en dispositivos separados enviarse mensajes entre sí usando WebRTC. Nuestra aplicación tendrá dos páginas. Uno para iniciar sesión y otro para enviar mensajes a otro usuario.

Las dos páginas serán las etiquetas div . La mayor parte de la entrada se realiza a través de controladores de eventos simples.

Servidor de señalización

Para crear una conexión WebRTC, los clientes deben poder transferir mensajes sin utilizar una conexión de pares WebRTC. Aquí es donde usaremos HTML5 WebSockets, una conexión de socket bidireccional entre dos puntos finales, un servidor web y un navegador web. Ahora comencemos a usar la biblioteca WebSocket. Cree el archivo server.js e inserte el siguiente código:

//require our websocket library 
var WebSocketServer = require('ws').Server; 

//creating a websocket server at port 9090 
var wss = new WebSocketServer({port: 9090});
  
//when a user connects to our sever 
wss.on('connection', function(connection) { 
   console.log("user connected"); 
	
   //when server gets a message from a connected user 
   connection.on('message', function(message) { 
      console.log("Got message from a user:", message); 
   }); 
	
   connection.send("Hello from server"); 
});

La primera línea requiere la biblioteca WebSocket que ya hemos instalado. Luego creamos un servidor de socket en el puerto 9090. Luego, escuchamos el evento de conexión . Este código se ejecutará cuando un usuario establezca una conexión WebSocket al servidor. Luego escuchamos los mensajes enviados por el usuario. Finalmente, enviamos una respuesta al usuario conectado diciendo "Hola desde el servidor".

En nuestro servidor de señalización, usaremos un nombre de usuario basado en cadenas para cada conexión para que sepamos dónde enviar mensajes. Cambiemos un poco nuestro controlador de conexión :

connection.on('message', function(message) { 
   var data; 
	
   //accepting only JSON messages 
   try { 
      data = JSON.parse(message); 
   } catch (e) { 
      console.log("Invalid JSON");
      data = {}; 
   } 
});

De esta forma solo aceptamos mensajes JSON. A continuación, necesitamos almacenar a todos los usuarios conectados en algún lugar. Usaremos un objeto Javascript simple para ello. Cambie la parte superior de nuestro archivo -

//require our websocket library 
var WebSocketServer = require('ws').Server; 

//creating a websocket server at port 9090 
var wss = new WebSocketServer({port: 9090}); 

//all connected to the server users 
var users = {};

Vamos a agregar un campo de tipo para cada mensaje proveniente del cliente. Por ejemplo, si un usuario desea iniciar sesión, envía el mensaje de tipo de inicio de sesión . Vamos a definirlo -

connection.on('message', function(message) { 
   var data; 
	
   //accepting only JSON messages 
   try { 
      data = JSON.parse(message); 
   } catch (e) { 
      console.log("Invalid JSON"); 
      data = {}; 
   }
	
   //switching type of the user message 
   switch (data.type) { 
      //when a user tries to login 
      case "login": 
         console.log("User logged:", data.name); 
			
         //if anyone is logged in with this username then refuse 
         if(users[data.name]) { 
            sendTo(connection, { 
               type: "login", 
               success: false
            }); 
         } else { 
            //save user connection on the server 
            users[data.name] = connection; 
            connection.name = data.name; 
				
            sendTo(connection, { 
               type: "login", 
               success: true 
            }); 
         } 
			
         break;
			
      default: 
         sendTo(connection, { 
            type: "error", 
            message: "Command no found: " + data.type 
         }); 
			
         break;
   } 
});

Si el usuario envía un mensaje con el tipo de inicio de sesión , nosotros:

  • Compruebe si alguien ya ha iniciado sesión con este nombre de usuario.
  • Si es así, dígale al usuario que no ha iniciado sesión correctamente.
  • Si nadie está usando este nombre de usuario, agregamos el nombre de usuario como clave para el objeto de conexión.
  • Si no se reconoce un comando, enviamos un error.

El siguiente código es una función auxiliar para enviar mensajes a una conexión. Añadirlo a la server.js archivo -

function sendTo(connection, message) { 
   connection.send(JSON.stringify(message)); 
}

Cuando el usuario se desconecta debemos limpiar su conexión. Podemos eliminar al usuario cuando se dispara el evento de cierre . Agregue el siguiente código al controlador de conexión :

connection.on("close", function() { 
   if(connection.name) { 
      delete users[connection.name]; 
   } 
});

Después de iniciar sesión correctamente, el usuario desea llamar a otro. Debería hacer una oferta a otro usuario para lograrlo. Agregar el controlador de ofertas -

case "offer": 
   //for ex. UserA wants to call UserB 
   console.log("Sending offer to: ", data.name); 
	
   //if UserB exists then send him offer details 
   var conn = users[data.name]; 
	
   if(conn != null){ 
   //setting that UserA connected with UserB 
   connection.otherName = data.name; 
	
      sendTo(conn, { 
         type: "offer", 
         offer: data.offer, 
         name: connection.name 
      }); 
     
   break;

En primer lugar, obtenemos la conexión del usuario al que intentamos llamar. Si existe le enviamos los detalles de la oferta . También agregamos otherName al objeto de conexión . Esto se hace por la simplicidad de encontrarlo más tarde.

Responder a la respuesta tiene un patrón similar al que usamos en el controlador de ofertas . Nuestro servidor simplemente pasa todos los mensajes como respuesta a otro usuario. Agregue el siguiente código después del controlador de ofertas :

case "answer": 
   console.log("Sending answer to: ", data.name); 
	
   //for ex. UserB answers UserA 
   var conn = users[data.name]; 
	
   if(conn != null) { 
      connection.otherName = data.name;
      sendTo(conn, { 
         type: "answer", 
         answer: data.answer 
      }); 
   } 
	
   break;

La parte final es el manejo del candidato ICE entre usuarios. Usamos la misma técnica simplemente pasando mensajes entre usuarios. La principal diferencia es que los mensajes candidatos pueden aparecer varias veces por usuario en cualquier orden. Agregar el controlador de candidatos -

case "candidate": 
   console.log("Sending candidate to:",data.name); 
   var conn = users[data.name];
	
   if(conn != null) { 
      sendTo(conn, { 
         type: "candidate", 
         candidate: data.candidate 
      }); 
   } 
	
   break;

Para permitir que nuestros usuarios se desconecten de otro usuario, debemos implementar la función de colgar. También le indicará al servidor que elimine todas las referencias de usuario. Agregue el controlador de licencia -

case "leave": 
   console.log("Disconnecting from", data.name); 
   var conn = users[data.name]; 
   conn.otherName = null;
	
   //notify the other user so he can disconnect his peer connection 
   if(conn != null) { 
      sendTo(conn, { 
         type: "leave" 
      }); 
   }
	
   break;

Esto también enviará al otro usuario el evento de licencia para que pueda desconectar su conexión de igual en consecuencia. También deberíamos manejar el caso cuando un usuario pierde su conexión del servidor de señalización. Modifiquemos nuestro controlador cercano -

connection.on("close", function() { 

   if(connection.name) { 
      delete users[connection.name]; 
		
      if(connection.otherName) { 
         console.log("Disconnecting from ", connection.otherName); 
         var conn = users[connection.otherName]; 
         conn.otherName = null;  
			
         if(conn != null) { 
            sendTo(conn, { 
               type: "leave" 
            }); 
         }  
      } 
   } 
});

El siguiente es el código completo de nuestro servidor de señalización:

//require our websocket library 
var WebSocketServer = require('ws').Server;
 
//creating a websocket server at port 9090 
var wss = new WebSocketServer({port: 9090}); 

//all connected to the server users 
var users = {};
  
//when a user connects to our sever 
wss.on('connection', function(connection) {
  
   console.log("User connected");
	 
   //when server gets a message from a connected user 
   connection.on('message', function(message) {
	 
      var data; 
      //accepting only JSON messages 
      try { 
         data = JSON.parse(message); 
      } catch (e) { 
         console.log("Invalid JSON"); 
         data = {}; 
      }
		  
      //switching type of the user message 
      switch (data.type) { 
         //when a user tries to login 
         case "login": 
            console.log("User logged", data.name); 
            //if anyone is logged in with this username then refuse 
            if(users[data.name]) { 
               sendTo(connection, { 
                  type: "login", 
                  success: false 
               }); 
            } else { 
               //save user connection on the server 
               users[data.name] = connection; 
               connection.name = data.name; 
					
                  sendTo(connection, { 
                     type: "login", 
                     success: true 
                  }); 
            }
				
            break;
				
         case "offer": 
            //for ex. UserA wants to call UserB 
            console.log("Sending offer to: ", data.name); 
				
            //if UserB exists then send him offer details 
            var conn = users[data.name]; 
				
            if(conn != null) { 
               //setting that UserA connected with UserB 
               connection.otherName = data.name; 
					
               sendTo(conn, { 
                  type: "offer", 
                  offer: data.offer, 
                  name: connection.name 
               }); 
            } 
				
            break;
				
         case "answer": 
            console.log("Sending answer to: ", data.name); 
            //for ex. UserB answers UserA 
            var conn = users[data.name]; 
				
            if(conn != null) { 
               connection.otherName = data.name; 
               sendTo(conn, { 
                  type: "answer", 
                  answer: data.answer 
               }); 
            } 
				
            break;
				
         case "candidate": 
            console.log("Sending candidate to:",data.name);
            var conn = users[data.name];  
				
            if(conn != null) { 
               sendTo(conn, { 
                  type: "candidate", 
                  candidate: data.candidate 
               }); 
            } 
				
            break;
				
         case "leave": 
            console.log("Disconnecting from", data.name); 
            var conn = users[data.name]; 
            conn.otherName = null; 
				
            //notify the other user so he can disconnect his peer connection 
            if(conn != null) { 
               sendTo(conn, { 
                  type: "leave"
               });
            }  
				
            break;
				
         default: 
            sendTo(connection, { 
               type: "error", 
               message: "Command not found: " + data.type 
            }); 
				
            break;
				
      }  
   });
	
   //when user exits, for example closes a browser window 
   //this may help if we are still in "offer","answer" or "candidate" state 
   connection.on("close", function() { 
	
      if(connection.name) { 
         delete users[connection.name]; 
			
         if(connection.otherName) { 
            console.log("Disconnecting from ", connection.otherName); 
            var conn = users[connection.otherName]; 
            conn.otherName = null;
				
            if(conn != null) { 
               sendTo(conn, { 
                  type: "leave" 
               }); 
            }  
         } 
      } 
   });
	
   connection.send("Hello world");
	
});
  
function sendTo(connection, message) { 
   connection.send(JSON.stringify(message)); 
}

Solicitud de cliente

Una forma de probar esta aplicación es abrir dos pestañas del navegador e intentar enviarse un mensaje entre sí.

En primer lugar, necesitamos instalar la biblioteca de arranque . Bootstrap es un marco frontend para desarrollar aplicaciones web. Puedes aprender más enhttp://getbootstrap.com/.Cree una carpeta llamada, por ejemplo, "chat de texto". Esta será nuestra carpeta raíz de la aplicación. Dentro de esta carpeta, cree un archivo package.json (es necesario para administrar las dependencias de npm) y agregue lo siguiente:

{ 
   "name": "webrtc-textochat", 
   "version": "0.1.0", 
   "description": "webrtc-textchat", 
   "author": "Author", 
   "license": "BSD-2-Clause" 
}

Luego ejecute npm install bootstrap . Esto instalará la biblioteca bootstrap en la carpeta textchat / node_modules .

Ahora necesitamos crear una página HTML básica. Cree un archivo index.html en la carpeta raíz con el siguiente código:

<html> 

   <head> 
      <title>WebRTC Text Demo</title> 
      <link rel = "stylesheet" href = "node_modules/bootstrap/dist/css/bootstrap.min.css"/> 
   </head>
 
   <style> 
      body { 
         background: #eee; 
         padding: 5% 0; 
      } 
   </style>
	
   <body> 
      <div id = "loginPage" class = "container text-center"> 
		
         <div class = "row"> 
            <div class = "col-md-4 col-md-offset-4"> 
               <h2>WebRTC Text Demo. Please sign in</h2> 
               <label for = "usernameInput" class = "sr-only">Login</label> 
               <input type = "email" id = "usernameInput" 
                  class = "form-control formgroup" placeholder = "Login" 
                  required = "" autofocus = "">
               <button id = "loginBtn" class = "btn btn-lg btn-primary btnblock">
                  Sign in</button> 
            </div> 
         </div> 
			
      </div>
		
      <div id = "callPage" class = "call-page container">
		
         <div class = "row"> 
            <div class = "col-md-4 col-md-offset-4 text-center"> 
               <div class = "panel panel-primary"> 
                  <div class = "panel-heading">Text chat</div> 
                  <div id = "chatarea" class = "panel-body text-left"></div> 
               </div> 
            </div> 
         </div>
			
         <div class = "row text-center form-group"> 
            <div class = "col-md-12"> 
               <input id = "callToUsernameInput" type = "text" 
                  placeholder = "username to call" /> 
               <button id = "callBtn" class = "btn-success btn">Call</button> 
               <button id = "hangUpBtn" class = "btn-danger btn">Hang Up</button> 
            </div> 
         </div> 
			
         <div class = "row text-center"> 
            <div class = "col-md-12"> 
               <input id = "msgInput" type = "text" placeholder = "message" /> 
               <button id = "sendMsgBtn" class = "btn-success btn">Send</button> 
            </div> 
         </div>
			
      </div> 
		
      <script src = "client.js"></script> 
		
   </body>
 
</html>

Esta página debería resultarle familiar. Hemos agregado el archivo bootstrap css. También hemos definido dos páginas. Finalmente, hemos creado varios campos de texto y botones para obtener información del usuario. En la página de "chat" debería ver la etiqueta div con el ID "chatarea" donde se mostrarán todos nuestros mensajes. Observe que hemos agregado un enlace a un archivo client.js .

Ahora necesitamos establecer una conexión con nuestro servidor de señalización. Cree el archivo client.js en la carpeta raíz con el siguiente código:

//our username 
var name; 
var connectedUser;
  
//connecting to our signaling server 
var conn = new WebSocket('ws://localhost:9090');
  
conn.onopen = function () { 
   console.log("Connected to the signaling server"); 
}; 
 
//when we got a message from a signaling server 
conn.onmessage = function (msg) { 
   console.log("Got message", msg.data);
	
   var data = JSON.parse(msg.data);
	
   switch(data.type) { 
      case "login": 
         handleLogin(data.success); 
         break; 
      //when somebody wants to call us 
      case "offer": 
         handleOffer(data.offer, data.name); 
         break; 
      case "answer": 
         handleAnswer(data.answer); 
         break; 
      //when a remote peer sends an ice candidate to us 
      case "candidate":
         handleCandidate(data.candidate); 
         break; 
      case "leave": 
         handleLeave(); 
         break; 
      default: 
         break; 
   } 
};
  
conn.onerror = function (err) { 
   console.log("Got error", err); 
};
  
//alias for sending JSON encoded messages 
function send(message) { 
   //attach the other peer username to our messages 
   if (connectedUser) { 
      message.name = connectedUser; 
   } 
	
   conn.send(JSON.stringify(message)); 
};

Ahora ejecute nuestro servidor de señalización a través del servidor de nodo . Luego, dentro de la carpeta raíz, ejecute el comando estático y abra la página dentro del navegador. Debería ver la siguiente salida de la consola:

El siguiente paso es implementar un inicio de sesión de usuario con un nombre de usuario único. Simplemente enviamos un nombre de usuario al servidor, que luego nos dice si está tomado o no. Agregue el siguiente código a su archivo client.js :

//****** 
//UI selectors block
//****** 

var loginPage = document.querySelector('#loginPage'); 
var usernameInput = document.querySelector('#usernameInput'); 
var loginBtn = document.querySelector('#loginBtn'); 

var callPage = document.querySelector('#callPage'); 
var callToUsernameInput = document.querySelector('#callToUsernameInput'); 
var callBtn = document.querySelector('#callBtn'); 

var hangUpBtn = document.querySelector('#hangUpBtn'); 
callPage.style.display = "none"; 

// Login when the user clicks the button 
loginBtn.addEventListener("click", function (event) { 
   name = usernameInput.value;
	
   if (name.length > 0) { 
      send({ 
         type: "login", 
         name: name 
      }); 
   } 
	
});
 
function handleLogin(success) { 

   if (success === false) { 
      alert("Ooops...try a different username"); 
   } else { 
      loginPage.style.display = "none"; 
      callPage.style.display = "block"; 
		
      //********************** 
      //Starting a peer connection 
      //********************** 
   } 
	
};

En primer lugar, seleccionamos algunas referencias a los elementos de la página. Ocultamos la página de llamadas. Luego, agregamos un detector de eventos en el botón de inicio de sesión. Cuando el usuario hace clic en él, enviamos su nombre de usuario al servidor. Finalmente, implementamos la devolución de llamada handleLogin. Si el inicio de sesión fue exitoso, mostramos la página de llamada, configuramos una conexión entre pares y creamos un canal de datos.

Para iniciar una conexión entre pares con un canal de datos, necesitamos:

  • Cree el objeto RTCPeerConnection
  • Cree un canal de datos dentro de nuestro objeto RTCPeerConnection

Agregue el siguiente código al "bloque de selectores de UI" -

var msgInput = document.querySelector('#msgInput'); 
var sendMsgBtn = document.querySelector('#sendMsgBtn'); 
var chatArea = document.querySelector('#chatarea'); 

var yourConn; 
var dataChannel;

Modificar la función handleLogin -

function handleLogin(success) { 
   if (success === false) { 
      alert("Ooops...try a different username"); 
   } else { 
      loginPage.style.display = "none"; 
      callPage.style.display = "block";
		
      //********************** 
      //Starting a peer connection 
      //**********************
		
      //using Google public stun server 
      var configuration = { 
         "iceServers": [{ "url": "stun:stun2.1.google.com:19302" }] 
      }; 
		
      yourConn = new webkitRTCPeerConnection(configuration, {optional: [{RtpDataChannels: true}]});
		
      // Setup ice handling 
      yourConn.onicecandidate = function (event) { 
         if (event.candidate) { 
            send({
               type: "candidate", 
               candidate: event.candidate 
            }); 
         } 
      };
		
      //creating data channel 
      dataChannel = yourConn.createDataChannel("channel1", {reliable:true}); 
		
      dataChannel.onerror = function (error) { 
         console.log("Ooops...error:", error); 
      };
		
      //when we receive a message from the other peer, display it on the screen 
      dataChannel.onmessage = function (event) { 
         chatArea.innerHTML += connectedUser + ": " + event.data + "<br />"; 
      };
		
      dataChannel.onclose = function () { 
         console.log("data channel is closed"); 
      };  
   } 
};

Si el inicio de sesión fue exitoso, la aplicación crea el objeto RTCPeerConnection y configura un controlador de candidatos que envía todos los candidatos a hielo encontrados al otro par. También crea un dataChannel. Tenga en cuenta que al crear el objeto RTCPeerConnection, el segundo argumento en el constructor es opcional: [{RtpDataChannels: true}] es obligatorio si está utilizando Chrome u Opera. El siguiente paso es crear una oferta para el otro compañero. Una vez que un usuario recibe la oferta, crea una respuesta y comienza a intercambiar candidatos de ICE. Agregue el siguiente código al archivo client.js :

//initiating a call
callBtn.addEventListener("click", function () { 
   var callToUsername = callToUsernameInput.value;
	
   if (callToUsername.length > 0) {
	
      connectedUser = callToUsername;
		
      // create an offer 
      yourConn.createOffer(function (offer) { 
		
         send({ 
            type: "offer", 
            offer: offer 
         }); 
			
         yourConn.setLocalDescription(offer); 
			
      }, function (error) { 
         alert("Error when creating an offer"); 
      });  
   } 
});
  
//when somebody sends us an offer 
function handleOffer(offer, name) { 
   connectedUser = name; 
   yourConn.setRemoteDescription(new RTCSessionDescription(offer));
	
   //create an answer to an offer 
   yourConn.createAnswer(function (answer) { 
      yourConn.setLocalDescription(answer); 
		
      send({ 
         type: "answer", 
         answer: answer 
      }); 
		
   }, function (error) { 
      alert("Error when creating an answer"); 
   });
};
  
//when we got an answer from a remote user 
function handleAnswer(answer) { 
   yourConn.setRemoteDescription(new RTCSessionDescription(answer)); 
};
  
//when we got an ice candidate from a remote user 
function handleCandidate(candidate) { 
   yourConn.addIceCandidate(new RTCIceCandidate(candidate)); 
};

Agregamos un controlador de clic al botón Llamar, que inicia una oferta. Luego implementamos varios controladores esperados por el controlador onmessage . Se procesarán de forma asincrónica hasta que ambos usuarios se hayan conectado.

El siguiente paso es implementar la función de colgar. Esto dejará de transmitir datos y le dirá al otro usuario que cierre el canal de datos. Agregue el siguiente código:

//hang up 
hangUpBtn.addEventListener("click", function () { 
   send({ 
      type: "leave" 
   }); 
	
   handleLeave(); 
}); 
 
function handleLeave() { 
   connectedUser = null; 
   yourConn.close(); 
   yourConn.onicecandidate = null; 
};

Cuando el usuario hace clic en el botón Colgar:

  • Enviará un mensaje de "dejar" al otro usuario.
  • Cerrará RTCPeerConnection y también el canal de datos.

El último paso es enviar un mensaje a otro par. Agregue el controlador de "clic" al botón "enviar mensaje" -

//when user clicks the "send message" button 
sendMsgBtn.addEventListener("click", function (event) { 
   var val = msgInput.value; 
   chatArea.innerHTML += name + ": " + val + "<br />"; 
	
   //sending a message to a connected peer 
   dataChannel.send(val); 
   msgInput.value = ""; 
});

Ahora ejecuta el código. Debería poder iniciar sesión en el servidor utilizando dos pestañas del navegador. A continuación, puede configurar una conexión entre pares con el otro usuario y enviarle un mensaje, así como cerrar el canal de datos haciendo clic en el botón "Colgar".

El siguiente es el archivo client.js completo :

//our username 
var name; 
var connectedUser; 

//connecting to our signaling server 
var conn = new WebSocket('ws://localhost:9090'); 

conn.onopen = function () { 
   console.log("Connected to the signaling server");
};
 
//when we got a message from a signaling server 
conn.onmessage = function (msg) { 
   console.log("Got message", msg.data); 
   var data = JSON.parse(msg.data); 
	
   switch(data.type) { 
      case "login": 
         handleLogin(data.success); 
         break; 
      //when somebody wants to call us 
      case "offer": 
         handleOffer(data.offer, data.name); 
         break; 
      case "answer": 
         handleAnswer(data.answer); 
         break; 
      //when a remote peer sends an ice candidate to us 
      case "candidate": 
         handleCandidate(data.candidate); 
         break; 
      case "leave": 
         handleLeave(); 
         break; 
      default: 
         break; 
   } 
}; 

conn.onerror = function (err) { 
   console.log("Got error", err); 
}; 

//alias for sending JSON encoded messages 
function send(message) { 

   //attach the other peer username to our messages
   if (connectedUser) { 
      message.name = connectedUser; 
   } 
	
   conn.send(JSON.stringify(message)); 
};
 
//****** 
//UI selectors block 
//****** 

var loginPage = document.querySelector('#loginPage'); 
var usernameInput = document.querySelector('#usernameInput'); 
var loginBtn = document.querySelector('#loginBtn'); 

var callPage = document.querySelector('#callPage'); 
var callToUsernameInput = document.querySelector('#callToUsernameInput');
var callBtn = document.querySelector('#callBtn'); 

var hangUpBtn = document.querySelector('#hangUpBtn'); 
var msgInput = document.querySelector('#msgInput'); 
var sendMsgBtn = document.querySelector('#sendMsgBtn'); 

var chatArea = document.querySelector('#chatarea'); 
var yourConn; 
var dataChannel; 
callPage.style.display = "none"; 

// Login when the user clicks the button 
loginBtn.addEventListener("click", function (event) { 
   name = usernameInput.value; 
	
   if (name.length > 0) { 
      send({ 
         type: "login", 
         name: name 
      }); 
   } 
	
});
 
function handleLogin(success) { 

   if (success === false) {
      alert("Ooops...try a different username"); 
   } else { 
      loginPage.style.display = "none"; 
      callPage.style.display = "block"; 
		
      //********************** 
      //Starting a peer connection 
      //********************** 
		
      //using Google public stun server 
      var configuration = { 
         "iceServers": [{ "url": "stun:stun2.1.google.com:19302" }] 
      }; 
		
      yourConn = new webkitRTCPeerConnection(configuration, {optional: [{RtpDataChannels: true}]}); 
		
      // Setup ice handling 
      yourConn.onicecandidate = function (event) { 
         if (event.candidate) { 
            send({ 
               type: "candidate", 
               candidate: event.candidate 
            }); 
         } 
      }; 
		
      //creating data channel 
      dataChannel = yourConn.createDataChannel("channel1", {reliable:true}); 
		
      dataChannel.onerror = function (error) { 
         console.log("Ooops...error:", error); 
      }; 
		
      //when we receive a message from the other peer, display it on the screen 
      dataChannel.onmessage = function (event) { 
         chatArea.innerHTML += connectedUser + ": " + event.data + "<br />"; 
      }; 
		
      dataChannel.onclose = function () { 
         console.log("data channel is closed"); 
      };
		
   } 
};
 
//initiating a call 
callBtn.addEventListener("click", function () { 
   var callToUsername = callToUsernameInput.value; 
	
   if (callToUsername.length > 0) { 
      connectedUser = callToUsername; 
      // create an offer 
      yourConn.createOffer(function (offer) { 
         send({ 
            type: "offer", 
            offer: offer 
         }); 
         yourConn.setLocalDescription(offer); 
      }, function (error) { 
         alert("Error when creating an offer"); 
      }); 
   } 
	
});
 
//when somebody sends us an offer 
function handleOffer(offer, name) { 
   connectedUser = name; 
   yourConn.setRemoteDescription(new RTCSessionDescription(offer)); 
	
   //create an answer to an offer 
   yourConn.createAnswer(function (answer) { 
      yourConn.setLocalDescription(answer); 
      send({ 
         type: "answer", 
         answer: answer 
      }); 
   }, function (error) { 
      alert("Error when creating an answer"); 
   });
	
};
 
//when we got an answer from a remote user 
function handleAnswer(answer) { 
   yourConn.setRemoteDescription(new RTCSessionDescription(answer)); 
};
 
//when we got an ice candidate from a remote user 
function handleCandidate(candidate) { 
   yourConn.addIceCandidate(new RTCIceCandidate(candidate)); 
};
 
//hang up 
hangUpBtn.addEventListener("click", function () { 
   send({ 
      type: "leave" 
   }); 
	
   handleLeave(); 
}); 

function handleLeave() { 
   connectedUser = null; 
   yourConn.close(); 
   yourConn.onicecandidate = null; 
};
 
//when user clicks the "send message" button 
sendMsgBtn.addEventListener("click", function (event) { 
   var val = msgInput.value; 
   chatArea.innerHTML += name + ": " + val + "<br />"; 
	
   //sending a message to a connected peer 
   dataChannel.send(val); 
   msgInput.value = ""; 
});

En este capítulo, agregaremos características de seguridad al servidor de señalización que creamos en el capítulo “Señalización WebRTC”. Habrá dos mejoras:

  • Autenticación de usuarios mediante la base de datos de Redis
  • Habilitación de una conexión de enchufe segura

En primer lugar, debe instalar Redis.

  • Descargue la última versión estable en http://redis.io/download(3.05 en mi caso)

  • Desempaquetarlo

  • Dentro de la carpeta descargada, ejecute sudo make install

  • Una vez finalizada la instalación, ejecute make test para comprobar si todo funciona correctamente.

Redis tiene dos comandos ejecutables:

  • redis-cli - interfaz de línea de comandos para Redis (parte del cliente)

  • redis-server - Almacén de datos de Redis

Para ejecutar el servidor Redis, escriba redis-server en la consola del terminal. Debería ver lo siguiente:

Ahora abra una nueva ventana de terminal y ejecute redis-cli para abrir una aplicación cliente.

Básicamente, Redis es una base de datos de valores clave. Para crear una clave con un valor de cadena, debe usar el comando SET. Para leer el valor de la clave, debe usar el comando GET. Agreguemos dos usuarios y contraseñas para ellos. Las claves serán los nombres de usuario y los valores de estas claves serán las contraseñas correspondientes.

Ahora deberíamos modificar nuestro servidor de señalización para agregar una autenticación de usuario. Agregue el siguiente código en la parte superior del archivo server.js :

//require the redis library in Node.js 
var redis = require("redis");
 
//creating the redis client object 
var redisClient = redis.createClient();

En el código anterior, requerimos la biblioteca de Redis para Node.js y la creación de un cliente de Redis para nuestro servidor.

Para agregar la autenticación, modifique el controlador de mensajes en el objeto de conexión:

//when a user connects to our sever 
wss.on('connection', function(connection) { 
   console.log("user connected");
	
   //when server gets a message from a connected user 
   connection.on('message', function(message) { 
	
      var data; 
      //accepting only JSON messages 
      try { 
         data = JSON.parse(message); 
      } catch (e) { 
         console.log("Invalid JSON"); 
         data = {}; 
      }
		
      //check whether a user is authenticated 
      if(data.type != "login") { 
		
         //if user is not authenticated 
         if(!connection.isAuth) { 
            sendTo(connection, { 
               type: "error", 
               message: "You are not authenticated" 
            }); 
            return; 
         } 
      } 
		
      //switching type of the user message 
      switch (data.type) { 
         //when a user tries to login 
         case "login": 
            console.log("User logged:", data.name); 
            //get password for this username from redis database 
				
            redisClient.get(data.name, function(err, reply) {  
               //check if password matches with the one stored in redis 
               var loginSuccess = reply === data.password;
				
               //if anyone is logged in with this username or incorrect password 
                  then refuse 
               if(users[data.name] || !loginSuccess) { 
                  sendTo(connection, { 
                     type: "login", 
                     success: false 
                  }); 
               } else { 
                  //save user connection on the server 
                  users[data.name] = connection; 
                  connection.name = data.name;
                  connection.isAuth = true; 
						
                  sendTo(connection, { 
                     type: "login", 
                     success: true 
                  }); 
               }  
            }); 
				
            break;
      }
   });
	
}		
		
//... 
//*****other handlers*******

En el código anterior si un usuario intenta iniciar sesión obtenemos de Redis su contraseña, verificamos si coincide con la almacenada, y si tiene éxito almacenamos su nombre de usuario en el servidor. También agregamos la bandera isAuth a la conexión para verificar si el usuario está autenticado. Note este código -

//check whether a user is authenticated 
if(data.type != "login") { 

   //if user is not authenticated 
   if(!connection.isAuth) { 
      sendTo(connection, { 
         type: "error", 
         message: "You are not authenticated" 
      });
		
      return; 
   } 
}

Si un usuario no autenticado intenta enviar una oferta o dejar la conexión, simplemente le devolvemos un error.

El siguiente paso es habilitar una conexión de socket segura. Es muy recomendable para aplicaciones WebRTC. PKI (Public Key Infrastructure) es una firma digital de una CA (Autoridad de certificación). Luego, los usuarios verifican que la clave privada utilizada para firmar un certificado coincida con la clave pública del certificado de la CA. Para fines de desarrollo. utilizaremos un certificado de seguridad autofirmado.

Usaremos el archivo openssl. Es una herramienta de código abierto que implementa los protocolos SSL (Secure Sockets Layer) y TLS (Transport Layer Security). A menudo se instala de forma predeterminada en los sistemas Unix. Ejecute openssl versión -a para comprobar si está instalado.

Para generar claves de certificado de seguridad públicas y privadas, debe seguir los pasos que se detallan a continuación:

  • Generate a temporary server password key

openssl genrsa -des3 -passout pass:x -out server.pass.key 2048

  • Generate a server private key

openssl rsa -passin pass:12345 -in server.pass.key -out server.key

  • Generate a signing request. You will be asked additional questions about your company. Just hit the “Enter” button all the time.

openssl req -new -key server.key -out server.csr

  • Generate the certificate

openssl x509 -req -days 1095 -in server.csr -signkey server.key -out server.crt

Ahora tiene dos archivos, el certificado (server.crt) y la clave privada (server.key). Cópielos en la carpeta raíz del servidor de señalización.

Para habilitar la conexión segura, modifique nuestro servidor de señalización.

//require file system module 
var fs = require('fs'); 
var httpServ = require('https');
  
//https://github.com/visionmedia/superagent/issues/205 
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
  
//out secure server will bind to the port 9090 
var cfg = { 
   port: 9090, 
   ssl_key: 'server.key', 
   ssl_cert: 'server.crt' 
};
  
//in case of http request just send back "OK" 
var processRequest = function(req, res) { 
   res.writeHead(200); 
   res.end("OK"); 
};
  
//create our server with SSL enabled 
var app = httpServ.createServer({ 
   key: fs.readFileSync(cfg.ssl_key), 
   cert: fs.readFileSync(cfg.ssl_cert) 
}, processRequest).listen(cfg.port);
	
//require our websocket library 
var WebSocketServer = require('ws').Server; 

//creating a websocket server at port 9090 
var wss = new WebSocketServer({server: app}); 

//all connected to the server users 
var users = {};
  
//require the redis library in Node.js
var redis = require("redis"); 

//creating the redis client object 
var redisClient = redis.createClient(); 
 
//when a user connects to our sever 
wss.on('connection', function(connection){ 
//...other code

En el código anterior, requerimos que la biblioteca fs lea la clave privada y el certificado, cree el objeto cfg con el puerto de enlace y las rutas para la clave privada y el certificado. Luego, creamos un servidor HTTPS con nuestras claves junto con el servidor WebSocket en el puerto 9090.

Ahora abierto https://localhost:9090en Opera. Debería ver lo siguiente:

Haga clic en el botón "continuar de todos modos". Debería ver el mensaje "OK".

Para probar nuestro servidor de señalización seguro, modificaremos la aplicación de chat que creamos en el tutorial "WebRTC Text Demo". Solo necesitamos agregar un campo de contraseña. El siguiente es el archivo index.html completo :

<html>
  
   <head> 
      <title>WebRTC Text Demo</title>  
      <link rel = "stylesheet" href = "node_modules/bootstrap/dist/css/bootstrap.min.css"/>  
   </head> 

   <style>  
      body { 
         background: #eee; 
         padding: 5% 0; 
      }  
   </style>
	
   <body>  
      <div id = "loginPage" class = "container text-center"> 
		
         <div class = "row"> 
            <div class = "col-md-4 col-md-offset-4">  
               <h2>WebRTC Text Demo. Please sign in</h2> 
               <label for = "usernameInput" class = "sr-only">Login</label> 
               <input type = "email" id = "usernameInput" 
                  class = "form-control formgroup" placeholder = "Login" 
                  required = "" autofocus = ""> 
               <input type = "text" id = "passwordInput" 
                  class = "form-control form-group" placeholder = "Password"
                  required = "" autofocus = ""> 
               <button id = "loginBtn" class = "btn btn-lg btn-primary btnblock"
                  >Sign in</button>  
            </div> 
         </div> 
			
      </div> 
		
      <div id = "callPage" class = "call-page container">
		
         <div class = "row"> 
            <div class = "col-md-4 col-md-offset-4 text-center"> 
               <div class = "panel panel-primary"> 
                  <div class = "panel-heading">Text chat</div> 
                  <div id = "chatarea" class = "panel-body text-left"></div> 
               </div> 
            </div> 
         </div>
			
         <div class = "row text-center form-group"> 
            <div class = "col-md-12"> 
               <input id = "callToUsernameInput" type = "text" 
                  placeholder = "username to call" /> 
               <button id = "callBtn" class = "btn-success btn">Call</button> 
               <button id = "hangUpBtn" class = "btn-danger btn">Hang Up</button> 
            </div> 
         </div>
			
         <div class = "row text-center"> 
            <div class = "col-md-12"> 
               <input id = "msgInput" type = "text" placeholder = "message" /> 
               <button id = "sendMsgBtn" class = "btn-success btn">Send</button> 
            </div> 
         </div>
			
      </div>  
		
      <script src = "client.js"></script> 
  
   </body> 
	
</html>

También necesitamos habilitar una conexión de socket segura en el archivo client.js a través de esta línea var conn = new WebSocket ('wss: // localhost: 9090'); . Observe el protocolo wss . Luego, el botón de inicio de sesión debe modificarse para enviar la contraseña junto con el nombre de usuario:

loginBtn.addEventListener("click", function (event) { 
   name = usernameInput.value; 
   var pwd = passwordInput.value;
	
   if (name.length > 0) { 
      send({ 
         type: "login", 
         name: name, 
         password: pwd 
      }); 
   } 
	
});

El siguiente es el archivo client.js completo :

//our username 
var name; 
var connectedUser;
  
//connecting to our signaling server 
var conn = new WebSocket('wss://localhost:9090');
  
conn.onopen = function () { 
   console.log("Connected to the signaling server"); 
};
  
//when we got a message from a signaling server 
conn.onmessage = function (msg) { 
   console.log("Got message", msg.data);
	
   var data = JSON.parse(msg.data);
	
   switch(data.type) { 
      case "login": 
         handleLogin(data.success); 
         break; 
      //when somebody wants to call us 
      case "offer":
         handleOffer(data.offer, data.name); 
         break; 
      case "answer": 
         handleAnswer(data.answer); 
         break; 
      //when a remote peer sends an ice candidate to us 
      case "candidate": 
         handleCandidate(data.candidate); 
         break; 
      case "leave": 
         handleLeave(); 
         break; 
      default: 
         break; 
   } 
};
  
conn.onerror = function (err) { 
   console.log("Got error", err); 
};  

//alias for sending JSON encoded messages 
function send(message) { 
   //attach the other peer username to our messages 
   if (connectedUser) { 
      message.name = connectedUser; 
   } 
	
   conn.send(JSON.stringify(message)); 
}; 
 
//****** 
//UI selectors block 
//******

var loginPage = document.querySelector('#loginPage'); 
var usernameInput = document.querySelector('#usernameInput'); 
var passwordInput = document.querySelector('#passwordInput'); 
var loginBtn = document.querySelector('#loginBtn'); 

var callPage = document.querySelector('#callPage'); 
var callToUsernameInput = document.querySelector('#callToUsernameInput');
var callBtn = document.querySelector('#callBtn'); 
var hangUpBtn = document.querySelector('#hangUpBtn');
  
var msgInput = document.querySelector('#msgInput'); 
var sendMsgBtn = document.querySelector('#sendMsgBtn'); 
var chatArea = document.querySelector('#chatarea'); 

var yourConn; 
var dataChannel;
  
callPage.style.display = "none";
  
// Login when the user clicks the button 
loginBtn.addEventListener("click", function (event) { 
   name = usernameInput.value; 
   var pwd = passwordInput.value;  
	
   if (name.length > 0) { 
      send({ 
         type: "login", 
         name: name, 
         password: pwd 
      }); 
   } 
	
}); 
 
function handleLogin(success) { 
   if (success === false) {
      alert("Ooops...incorrect username or password"); 
   } else { 
      loginPage.style.display = "none"; 
      callPage.style.display = "block";
		
      //********************** 
      //Starting a peer connection 
      //********************** 
		
      //using Google public stun server 
      var configuration = { 
         "iceServers": [{ "url": "stun:stun2.1.google.com:19302" }] 
      }; 
		
      yourConn = new webkitRTCPeerConnection(configuration, {optional: [{RtpDataChannels: true}]}); 
		
      // Setup ice handling 
      yourConn.onicecandidate = function (event) { 
         if (event.candidate) { 
            send({ 
               type: "candidate", 
               candidate: event.candidate 
            }); 
         } 
      };
		
      //creating data channel 
      dataChannel = yourConn.createDataChannel("channel1", {reliable:true}); 
		
      dataChannel.onerror = function (error) { 
         console.log("Ooops...error:", error); 
      };
		
      //when we receive a message from the other peer, display it on the screen 
      dataChannel.onmessage = function (event) { 
         chatArea.innerHTML += connectedUser + ": " + event.data + "<br />"; 
      };  
      dataChannel.onclose = function () { 
         console.log("data channel is closed"); 
      };  
   } 
	
};
  
//initiating a call 
callBtn.addEventListener("click", function () { 
   var callToUsername = callToUsernameInput.value;
	
   if (callToUsername.length > 0) {
	
      connectedUser = callToUsername;
		
      // create an offer 
      yourConn.createOffer(function (offer) { 
         send({ 
            type: "offer", 
            offer: offer 
         }); 
			
         yourConn.setLocalDescription(offer); 
			
      }, function (error) { 
         alert("Error when creating an offer"); 
      });  
   } 
});
 
//when somebody sends us an offer 
function handleOffer(offer, name) { 
   connectedUser = name; 
   yourConn.setRemoteDescription(new RTCSessionDescription(offer));
	
   //create an answer to an offer 
   yourConn.createAnswer(function (answer) { 
      yourConn.setLocalDescription(answer); 
		
      send({ 
         type: "answer", 
         answer: answer 
      }); 
		
   }, function (error) { 
      alert("Error when creating an answer"); 
   }); 
	
};
  
//when we got an answer from a remote user 
function handleAnswer(answer) { 
   yourConn.setRemoteDescription(new RTCSessionDescription(answer)); 
};
  
//when we got an ice candidate from a remote user 
function handleCandidate(candidate) { 
   yourConn.addIceCandidate(new RTCIceCandidate(candidate)); 
};
   
//hang up 
hangUpBtn.addEventListener("click", function () { 

   send({ 
      type: "leave"
   }); 
	
   handleLeave(); 
});
  
function handleLeave() { 
   connectedUser = null; 
   yourConn.close(); 
   yourConn.onicecandidate = null; 
}; 
 
//when user clicks the "send message" button 
sendMsgBtn.addEventListener("click", function (event) { 
   var val = msgInput.value; 
   chatArea.innerHTML += name + ": " + val + "<br />"; 
	
   //sending a message to a connected peer 
   dataChannel.send(val); 
   msgInput.value = ""; 
});

Ahora ejecute nuestro servidor de señalización seguro a través del servidor de nodo . Ejecute el nodo estático dentro de la carpeta de demostración de chat modificada. Abiertolocalhost:8080en dos pestañas del navegador. Intente iniciar sesión. Recuerde que solo "usuario1" con "contraseña1" y "usuario2" con "contraseña2" pueden iniciar sesión. Luego, establezca RTCPeerConnection (llame a otro usuario) e intente enviar un mensaje.

El siguiente es el código completo de nuestro servidor de señalización seguro:

//require file system module 
var fs = require('fs'); 
var httpServ = require('https');

//https://github.com/visionmedia/superagent/issues/205 
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
  
//out secure server will bind to the port 9090 
var cfg = { 
   port: 9090, 
   ssl_key: 'server.key', 
   ssl_cert: 'server.crt' 
};
  
//in case of http request just send back "OK" 
var processRequest = function(req, res){ 
   res.writeHead(200); 
   res.end("OK"); 
};
  
//create our server with SSL enabled 
var app = httpServ.createServer({ 
   key: fs.readFileSync(cfg.ssl_key), 
   cert: fs.readFileSync(cfg.ssl_cert) 
}, processRequest).listen(cfg.port);
  
//require our websocket library 
var WebSocketServer = require('ws').Server; 

//creating a websocket server at port 9090 
var wss = new WebSocketServer({server: app}); 

//all connected to the server users 
var users = {};
  
//require the redis library in Node.js 
var redis = require("redis"); 

//creating the redis client object 
var redisClient = redis.createClient();

//when a user connects to our sever 
wss.on('connection', function(connection) { 
   console.log("user connected"); 
	
   //when server gets a message from a connected user 
   connection.on('message', function(message) {  
	
      var data; 
      //accepting only JSON messages 
      try { 
         data = JSON.parse(message); 
      } catch (e) { 
         console.log("Invalid JSON"); 
         data = {}; 
      } 
		
      //check whether a user is authenticated 
      if(data.type != "login") { 
         //if user is not authenticated 
         if(!connection.isAuth) { 
            sendTo(connection, { 
               type: "error", 
               message: "You are not authenticated" 
            }); 
				
            return; 
         } 
      }
		
      //switching type of the user message 
      switch (data.type) { 
         //when a user tries to login 
         case "login":
            console.log("User logged:", data.name); 
            //get password for this username from redis database 
            redisClient.get(data.name, function(err, reply) {
			  
               //check if password matches with the one stored in redis 
               var loginSuccess = reply === data.password;
				  
               //if anyone is logged in with this username or incorrect password 
                  then refuse 
               if(users[data.name] || !loginSuccess) { 
                  sendTo(connection, { 
                     type: "login", 
                     success: false 
                  }); 
               } else { 
                  //save user connection on the server 
                  users[data.name] = connection; 
                  connection.name = data.name; 
                  connection.isAuth = true; 
						
                  sendTo(connection, { 
                     type: "login", 
                     success: true 
                  }); 
               }  
            }); 
				
            break;
				
         case "offer": 
            //for ex. UserA wants to call UserB 
            console.log("Sending offer to: ", data.name); 
				
            //if UserB exists then send him offer details 
            var conn = users[data.name];
				
            if(conn != null) { 
               //setting that UserA connected with UserB 
               connection.otherName = data.name;
					
               sendTo(conn, { 
                  type: "offer", 
                  offer: data.offer, 
                  name: connection.name 
               }); 
            } 
				
            break;
				
         case "answer": 
            console.log("Sending answer to: ", data.name); 
            //for ex. UserB answers UserA 
            var conn = users[data.name]; 
				
            if(conn != null) { 
               connection.otherName = data.name;
					
               sendTo(conn, { 
                  type: "answer", 
                  answer: data.answer 
               }); 
            } 
				
            break;
				
         case "candidate": 
            console.log("Sending candidate to:",data.name); 
            var conn = users[data.name];
				
            if(conn != null) { 
               sendTo(conn, { 
                  type: "candidate", 
                  candidate: data.candidate 
               });
            } 
				
            break;
				
         case "leave": 
            console.log("Disconnecting from", data.name); 
            var conn = users[data.name]; 
            conn.otherName = null; 
				
            //notify the other user so he can disconnect his peer connection 
            if(conn != null) { 
               sendTo(conn, { 
                  type: "leave" 
               }); 
            }  
				
            break;
				
         connection.on("close", function() {
			
            if(connection.name) { 
               delete users[connection.name]; 
					
               if(connection.otherName) { 
                  console.log("Disconnecting from ", connection.otherName); 
                  var conn = users[connection.otherName]; 
                  conn.otherName = null;  
						
                  if(conn != null) { 
                     sendTo(conn, { 
                        type: "leave" 
                    }); 
                  } 
						
               } 
            } 
         });
			
         default: 
            sendTo(connection, { 
               type: "error", 
               message: "Command no found: " + data.type 
            }); 
				
            break; 
      }  
   });
	
   //when user exits, for example closes a browser window 
   //this may help if we are still in "offer","answer" or "candidate" state 
   connection.on("close", function() { 
      if(connection.name) { 
         delete users[connection.name]; 
      } 
   });
	
   connection.send("Hello from server"); 
});
  
function sendTo(connection, message) { 
   connection.send(JSON.stringify(message)); 
}

Resumen

En este capítulo, agregamos autenticación de usuario a nuestro servidor de señalización. También aprendimos cómo crear certificados SSL autofirmados y usarlos en el ámbito de las aplicaciones WebRTC.