Habilita la comunicación en tiempo real con WebRTC

1. Antes de comenzar

En este codelab, aprenderás a compilar una app para obtener videos, tomar instantáneas con tu cámara web y compartirlas entre pares mediante WebRTC. También aprenderás a usar las API principales de WebRTC y configurar un servidor de mensajería con Node.js.

Prerequisites

  • Conocimientos básicos de HTML, CSS y JavaScript

Qué crearás

  • Obtén video con tu cámara web.
  • Reproducir video con RTCPeerConnection.
  • Transmite datos con RTCDataChannel.
  • Configurar un servicio de señalización para intercambiar mensajes
  • Combina las señales y la conexión del par.
  • Toma una foto y usa un canal de datos para compartirla.

Requisitos

  • Chrome 47 o una versión posterior
  • Servidor web para Chrome o el servidor web que elijas
  • El editor de texto que prefieras
  • Node.js

2. Obtén el código de muestra

Descarga el código

  1. Si estás familiarizado con Git, ejecuta este comando para clonar el código de este codelab desde GitHub:
git clone https://github.com/googlecodelabs/webrtc-web

De manera alternativa, haga clic en este vínculo para descargar un archivo ZIP del código:

Descargar código fuente

  1. Abre el archivo ZIP que se descargó a fin de descomprimir una carpeta del proyecto llamada webrtc-web-master, que contiene una carpeta para cada paso de este codelab y todos los recursos que necesitas.

Todo tu código funciona en el directorio llamado work.

Las carpetas step-nn contienen una versión finalizada para cada paso de este codelab. Están disponibles a modo de referencia.

Instala y verifica el servidor web

Si bien puedes usar tu propio servidor web, este codelab está diseñado para funcionar bien con Web Server for Chrome.

  1. Si no tienes un servidor web para Chrome, haz clic en este vínculo para instalarlo desde Chrome Web Store:

d0a4649b4920cf3.png

  1. Haz clic en Agregar a Chrome, que instala Web Server for Chrome y abre las apps de Google automáticamente en una pestaña nueva.
  2. Haga clic en Servidor web:

27fce4494f641883.png

Aparecerá un diálogo que te permite configurar tu servidor web local:

a300381a486b9e22.png

  1. Haz clic en Choose Folder.
  2. Selecciona la carpeta work que creaste.

En URL del servidor web, verás la URL en la que puedes ver tu trabajo en curso.

Chrome

  1. En Opciones (es posible que debas reiniciar), selecciona la casilla de verificación Mostrar automáticamente index.html.
  2. Activa o desactiva la opción Servidor web: Iniciada dos veces para detener y reiniciar el servidor.

f23cafb3993dfac1.png

  1. Haz clic en la URL en URL del servidor web para ver tu trabajo en el navegador web.

Deberías ver una página similar a work/index.html.

18a705cb6ccc5181.png

Obviamente, esta app aún no está haciendo nada interesante. Es solo un esqueleto mínimo para garantizar que tu servidor web funcione correctamente. Agregarás funciones en cuanto a la funcionalidad y el diseño en los pasos posteriores.

3. Transmite video desde tu cámara web

Encontrarás una versión completa de este paso en la carpeta step-01.

Agregue un guion de HTML

Copia este código y pégalo en el archivo index.html del directorio work para agregar un elemento video y script:

<!DOCTYPE html>
<html>

<head>

  <title>Real-time communication with WebRTC</title>

  <link rel="stylesheet" href="css/main.css" />

</head>

<body>

  <h1>Real-time communication with WebRTC</h1>

  <video autoplay playsinline></video>

  <script src="js/main.js"></script>

</body>

</html>

Agrega una pizca de JavaScript

Copia este código y pégalo en el archivo main.js de tu carpeta js:

'use strict';

// In this codelab, you  only stream video, not audio (video: true).
const mediaStreamConstraints = {
  video: true,
};

// The video element where the stream is displayed
const localVideo = document.querySelector('video');

// The local stream that's displayed on the video
let localStream;

// Handle success and add the MediaStream to the video element
function gotLocalMediaStream(mediaStream) {
  localStream = mediaStream;
  localVideo.srcObject = mediaStream;
}

// Handle error and log a message to the console with the error message
function handleLocalMediaStreamError(error) {
  console.log('navigator.getUserMedia error: ', error);
}

// Initialize media stream
navigator.mediaDevices.getUserMedia(mediaStreamConstraints)
  .then(gotLocalMediaStream).catch(handleLocalMediaStreamError);

Pruébalo

Abre el archivo index.html en el navegador. Deberías ver algo como esto, pero con la vista de tu cámara web, por supuesto:

9297048e43ed0f3d.png

Cómo funciona

Después de la llamada getUserMedia(), el navegador solicita permiso para acceder a la cámara si es la primera solicitud de acceso a la cámara para el origen actual.

Si se ejecuta correctamente, se muestra un MediaStream, que un elemento media puede usar a través del atributo srcObject:

navigator.mediaDevices.getUserMedia(mediaStreamConstraints)
  .then(gotLocalMediaStream).catch(handleLocalMediaStreamError);


}
function gotLocalMediaStream(mediaStream) {
  localVideo.srcObject = mediaStream;
}

El argumento constraints te permite especificar qué contenido multimedia se obtendrá. En este ejemplo, el contenido multimedia solo es video porque el audio está inhabilitado de forma predeterminada:

const mediaStreamConstraints = {
  video: true,
};

Puedes usar restricciones para requisitos adicionales, como la resolución de video:

const hdConstraints = {
  video: {
    width: {
      min: 1280
    },
    height: {
      min: 720
    }
  }
}

La especificación MediaTrackConstraints enumera todos los tipos de restricciones posibles, aunque no todas las opciones son compatibles con todos los navegadores. Si la resolución solicitada no es compatible con la cámara seleccionada actualmente, se rechazará getUserMedia() con una OverconstrainedError y se te solicitará que otorgues permiso para acceder a tu cámara.

Si se realiza correctamente getUserMedia(), la transmisión de video por Internet de la cámara web se configura como la fuente del elemento de video:

function gotLocalMediaStream(mediaStream) {
  localVideo.srcObject = mediaStream;
}

Obtén puntos adicionales

  • El objeto localStream que se pasa a getUserMedia() está dentro del alcance global, por lo que puedes inspeccionarlo desde la consola del navegador. Abre la consola, escribe stream, y presiona Enter (Return en Mac). Para ver la consola en Chrome, presiona Control+Shift+J (o Command+Option+J en Mac).
  • ¿Qué muestra localStream.getVideoTracks()?
  • Llamar a localStream.getVideoTracks()[0].stop()
  • Observa el objeto constraints. ¿Qué sucede cuando lo cambias a {audio: true, video: true}?
  • ¿De qué tamaño es el elemento de video? ¿Cómo puedes obtener el tamaño natural del video desde JavaScript en lugar del tamaño de la pantalla? Usa las Herramientas para desarrolladores de Google Chrome para comprobarlo.
  • Agrega filtros de CSS al elemento de video de la siguiente manera:
video {
  filter: blur(4px) invert(1) opacity(0.5);
}
  • Agrega filtros SVG, como el siguiente:
video {
   filter: hue-rotate(180deg) saturate(200%);
 }

Sugerencias

Práctica recomendada

Asegúrate de que el elemento de video no desborde su contenedor. En este codelab, se agregaron width y max-width a fin de establecer un tamaño preferido y un tamaño máximo para el video. Tu navegador calcula la altura automáticamente.

video {
  max-width: 100%;
  width: 320px;
}

4. Transmisión de video con la API de RTCPeerConnection

Encontrarás una versión completa de este paso en la carpeta step-2.

Cómo agregar elementos de video y botones de control

En el archivo index.html, reemplaza el único elemento video por dos elementos video y tres button:

<video id="localVideo" autoplay playsinline></video>
<video id="remoteVideo" autoplay playsinline></video>


<div>
  <button id="startButton">Start</button>
  <button id="callButton">Call</button>
  <button id="hangupButton">Hang Up</button>
</div>

Un elemento de video muestra la transmisión de getUserMedia(), y el otro muestra el mismo video transmitido a través de RTCPeerconnection. En una app real, un elemento video muestra la transmisión local y el otro, la remota.

Agrega el corrector de adaptadores.js

Copia este elemento de la secuencia de comandos y pégalo sobre el elemento de la secuencia de comandos para main.js:

<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>

Tu archivo index.html debería verse de la siguiente manera:

<!DOCTYPE html>
<html>

<head>
  <title>Real-time communication with WebRTC</title>
  <link rel="stylesheet" href="css/main.css" />
</head>

<body>
  <h1>Real-time communication with WebRTC</h1>

  <video id="localVideo" autoplay playsinline></video>
  <video id="remoteVideo" autoplay playsinline></video>

  <div>
    <button id="startButton">Start</button>
    <button id="callButton">Call</button>
    <button id="hangupButton">Hang Up</button>
  </div>

  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="js/main.js"></script>
</body>
</html>

Instala el código de RTCPeerConnection.

Reemplaza main.js por la versión en la carpeta step-02.

Realizar la llamada

  1. Abre el archivo index.html.
  2. Haz clic en Iniciar para obtener el video de tu cámara web.
  3. Haz clic en Llamar para establecer la conexión entre pares.

Deberías ver el mismo video de tu cámara web en los dos elementos de video.

  1. Consulta la consola del navegador para ver el registro de WebRTC.

Cómo funciona

Este paso hace mucho.

WebRTC usa la API de RTCPeerConnection para configurar una conexión a fin de transmitir video entre clientes de WebRTC, conocidos como pares.

En este ejemplo, los dos objetos RTCPeerConnection están en la misma página: pc1 y pc2.

La configuración de llamadas entre pares de WebRTC implica tres tareas:

  1. Crea un RTCPeerConnection para cada extremo de la llamada y, en cada extremo, agrega la transmisión local de getUserMedia().
  2. Obtén y comparte información de la red.

Los extremos de conexión potenciales se conocen como candidatos de ICE.

  1. Obtén y comparte descripciones locales y remotas.

Los metadatos sobre medios locales tienen el formato del protocolo de descripción de sesiones (SDP).

Imagina que Alice y Bob quieren usar RTCPeerConnection para configurar un chat de video.

Primero, Alice y Bob intercambian información de la red. La expresión buscar candidatos se refiere al proceso de encontrar interfaces y puertos de red con el marco de trabajo ICE.

  1. Alice crea un objeto RTCPeerConnection con un controlador onicecandidate (addEventListener('icecandidate')).

Esto corresponde al siguiente código de main.js:

let localPeerConnection;
localPeerConnection = new RTCPeerConnection(servers);
localPeerConnection.addEventListener('icecandidate', handleConnection);
localPeerConnection.addEventListener(
    'iceconnectionstatechange', handleConnectionChange);
  1. Alice llama a getUserMedia() y agrega la transmisión que se pasa a él:
navigator.mediaDevices.getUserMedia(mediaStreamConstraints).
  then(gotLocalMediaStream).
  catch(handleLocalMediaStreamError);
function gotLocalMediaStream(mediaStream) {
  localVideo.srcObject = mediaStream;
  localStream = mediaStream;
  trace('Received local stream.');
  callButton.disabled = false;  // Enable call button.
}
localPeerConnection.addStream(localStream);
trace('Added local stream to localPeerConnection.');
  1. Se llama al controlador onicecandidate del primer paso cuando están disponibles los candidatos de red.
  2. Alicia envía datos serializados de los candidatos a Roberto.

En una app real, este proceso, conocido como señalización, tiene lugar en un servicio de mensajería. Obtendrás más información sobre cómo hacerlo más adelante. Por supuesto, en este paso, los dos objetos RTCPeerConnection están en la misma página y se pueden comunicar directamente sin necesidad de mensajes externos.

  1. Cuando Roberto recibe un mensaje candidato de Alicia, llama a addIceCandidate() para agregar al candidato a la descripción remota de pares:
function handleConnection(event) {
  const peerConnection = event.target;
  const iceCandidate = event.candidate;

  if (iceCandidate) {
    const newIceCandidate = new RTCIceCandidate(iceCandidate);
    const otherPeer = getOtherPeer(peerConnection);

    otherPeer.addIceCandidate(newIceCandidate)
      .then(() => {
        handleConnectionSuccess(peerConnection);
      }).catch((error) => {
        handleConnectionFailure(peerConnection, error);
      });

    trace(`${getPeerName(peerConnection)} ICE candidate:\n` +
          `${event.candidate.candidate}.`);
  }
}

Los pares de WebRTC también deben descubrir e intercambiar información de medios locales y remotas de audio y video, como la resolución y las capacidades del códec. La señalización para intercambiar información de configuración de medios continúa con el intercambio de BLOB de metadatos, conocidos como una oferta y respuesta, con el formato SDP.

  1. Alice ejecuta el método RTCPeerConnection createOffer().

La promesa que se muestra proporciona un RTCSessionDescription: descripción de la sesión local de Alicia:

trace('localPeerConnection createOffer start.');
localPeerConnection.createOffer(offerOptions)
  .then(createdOffer).catch(setSessionDescriptionError);
  1. Si se aplica correctamente, Alice configura la descripción local con setLocalDescription() y luego envía esta descripción de la sesión a Roberto a través de su canal de señalización.
  2. Roberto configura la descripción que Alicia le envió como descripción remota con setRemoteDescription().
  3. Roberto ejecuta el método RTCPeerConnection createAnswer() y le pasa la descripción remota que recibió de Alice para que se genere una sesión local que sea compatible con la suya.
  4. La promesa createAnswer() pasa una RTCSessionDescription, que Bob configura como la descripción local y la envía a Alice.
  5. Cuando Alice obtiene la descripción de la sesión de Bob, la establece como la descripción remota con setRemoteDescription().
// Logs offer creation and sets peer connection session descriptions
function createdOffer(description) {
  trace(`Offer from localPeerConnection:\n${description.sdp}`);

  trace('localPeerConnection setLocalDescription start.');
  localPeerConnection.setLocalDescription(description)
    .then(() => {
      setLocalDescriptionSuccess(localPeerConnection);
    }).catch(setSessionDescriptionError);

  trace('remotePeerConnection setRemoteDescription start.');
  remotePeerConnection.setRemoteDescription(description)
    .then(() => {
      setRemoteDescriptionSuccess(remotePeerConnection);
    }).catch(setSessionDescriptionError);

  trace('remotePeerConnection createAnswer start.');
  remotePeerConnection.createAnswer()
    .then(createdAnswer)
    .catch(setSessionDescriptionError);
}

// Logs answer to offer creation and sets peer-connection session descriptions
function createdAnswer(description) {
  trace(`Answer from remotePeerConnection:\n${description.sdp}.`);

  trace('remotePeerConnection setLocalDescription start.');
  remotePeerConnection.setLocalDescription(description)
    .then(() => {
      setLocalDescriptionSuccess(remotePeerConnection);
    }).catch(setSessionDescriptionError);

  trace('localPeerConnection setRemoteDescription start.');
  localPeerConnection.setRemoteDescription(description)
    .then(() => {
      setRemoteDescriptionSuccess(localPeerConnection);
    }).catch(setSessionDescriptionError);
}

Obtén puntos adicionales

  1. Navega a chrome://webrtc-internals.

En esta página, se proporcionan estadísticas de WebRTC y datos de depuración. (Puedes encontrar una lista completa de las URL de Chrome en chrome://about).

  1. Diseñe el estilo de la página con CSS:
  2. Coloca los videos uno al lado del otro.
  3. Utilice botones más grandes para el mismo ancho.
  4. Asegúrate de que el diseño funcione en dispositivos móviles.
  5. En la consola de las Herramientas para desarrolladores de Chrome, observa localStream, localPeerConnection y remotePeerConnection.
  6. En la consola, observa localPeerConnectionpc1.localDescription.

¿Cómo es el formato SDP?

Sugerencias

  • Para obtener más información sobre el corrector de compatibilidad deAdapter.js, consulta el repositorio de GitHub de adapter.js.
  • Revisa AppRTC y su código, la app canónica del proyecto de WebRTC para llamadas de WebRTC. El tiempo de configuración de la llamada es inferior a 500 ms.

Práctica recomendada

A fin de preparar tu código para el futuro, usa las nuevas API basadas en promesas y habilita la compatibilidad con navegadores que no las admiten con adapter.js.

5. Usa un canal de datos para intercambiar datos

Encontrarás una versión completa de este paso en la carpeta step-03.

Actualiza tu código HTML

En este paso, usarás canales de datos de WebRTC para enviar texto entre dos elementos textarea en la misma página. Eso no es muy útil, pero demuestra cómo se puede usar WebRTC para compartir datos y transmitir video.

Quita los elementos video y button de index.html, y reemplázalos por el siguiente código HTML:

<textarea id="dataChannelSend" disabled
    placeholder="Press Start, enter some text, then press Send."></textarea>
<textarea id="dataChannelReceive" disabled></textarea>

<div id="buttons">
  <button id="startButton">Start</button>
  <button id="sendButton">Send</button>
  <button id="closeButton">Stop</button>
</div>

Un textarea se usa para ingresar texto, mientras que el otro es para mostrarlo como transmitido entre pares.

Tu archivo index.html debería verse de la siguiente manera:

<!DOCTYPE html>
<html>

<head>

  <title>Real-time communication with WebRTC</title>

  <link rel="stylesheet" href="css/main.css" />

</head>

<body>

  <h1>Real-time communication with WebRTC</h1>

  <textarea id="dataChannelSend" disabled
    placeholder="Press Start, enter some text, then press Send."></textarea>
  <textarea id="dataChannelReceive" disabled></textarea>

  <div id="buttons">
    <button id="startButton">Start</button>
    <button id="sendButton">Send</button>
    <button id="closeButton">Stop</button>
  </div>

  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="js/main.js"></script>

</body>

</html>

Actualiza tu código JavaScript

  1. Reemplaza main.js por el contenido de step-03/js/main.js.
  1. Prueba transmitir datos entre pares:
  2. Abre index.html.
  3. Haz clic en Iniciar para configurar la conexión de par.
  4. Ingresa texto en la textarea de la izquierda.
  5. Haz clic en Enviar para transferir el texto con un canal de datos WebRTC.

Cómo funciona

Este código usa RTCPeerConnection y RTCDataChannel para habilitar el intercambio de mensajes de texto.

La mayor parte del código de este paso es la misma que para el ejemplo de RTCPeerConnection. Las funciones sendData() y createConnection() tienen la mayor parte del código nuevo:

function createConnection() {
  dataChannelSend.placeholder = '';
  var servers = null;
  pcConstraint = null;
  dataConstraint = null;
  trace('Using SCTP based data channels');
  // For SCTP, reliable and ordered delivery is true by default.
  // Add localConnection to global scope to make it visible
  // from the browser console.
  window.localConnection = localConnection =
      new RTCPeerConnection(servers, pcConstraint);
  trace('Created local peer connection object localConnection');

  sendChannel = localConnection.createDataChannel('sendDataChannel',
      dataConstraint);
  trace('Created send data channel');

  localConnection.onicecandidate = iceCallback1;
  sendChannel.onopen = onSendChannelStateChange;
  sendChannel.onclose = onSendChannelStateChange;

  // Add remoteConnection to global scope to make it visible
  // from the browser console.
  window.remoteConnection = remoteConnection =
      new RTCPeerConnection(servers, pcConstraint);
  trace('Created remote peer connection object remoteConnection');

  remoteConnection.onicecandidate = iceCallback2;
  remoteConnection.ondatachannel = receiveChannelCallback;

  localConnection.createOffer().then(
    gotDescription1,
    onCreateSessionDescriptionError
  );
  startButton.disabled = true;
  closeButton.disabled = false;
}

function sendData() {
  var data = dataChannelSend.value;
  sendChannel.send(data);
  trace('Sent Data: ' + data);
}

La sintaxis de RTCDataChannel es similar a Deliberada con un método send() y un evento message.

Observa el uso de dataConstraint. Se pueden configurar los canales de datos para habilitar diferentes tipos de uso compartido de datos, como priorizar la entrega confiable sobre el rendimiento.

Obtén puntos adicionales

  1. Con SCTP, el protocolo que utilizan los canales de datos de WebRTC, la entrega de datos confiable y ordenada está activada de forma predeterminada. ¿En qué casos RTCDataChannel necesita proporcionar una entrega confiable de datos y cuándo el rendimiento debe ser más importante, incluso si eso significa perder algunos datos?
  2. Usa CSS para mejorar el diseño de la página y agrega un atributo de marcador de posición a la textarea de dataChannelReceive.
  3. Prueba la página en un dispositivo móvil.

Más información

6. Configurar un servicio de señalización para intercambiar mensajes

Aprendiste a intercambiar datos entre pares en la misma página, pero ¿cómo lo haces entre diferentes máquinas? Primero, debes configurar un canal de señalización para intercambiar mensajes de metadatos.

Encontrarás una versión completa de este paso en la carpeta step-04.

Acerca de la aplicación

WebRTC usa una API de JavaScript del lado del cliente, pero para el uso real también se requiere un servidor de señalización (mensajería), además de los servidores STUN y turn. Puedes obtener más información aquí.

En este paso, compilará un servidor de señalización de Node.js simple con el módulo Node.IO de Node.js y la biblioteca de JavaScript para la mensajería.

En este ejemplo, el servidor (la app de Node.js) se implementa en index.js, y el cliente que se ejecuta en él (la aplicación web) se implementa en index.html.

La app de Node.js en este paso tiene dos tareas.

En primer lugar, actúa como una retransmisión de mensajes:

socket.on('message', function (message) {
  log('Got message: ', message);
  socket.broadcast.emit('message', message);
});

En segundo lugar, administra las salas de chat de video de WebRTC:

if (numClients === 0) {
  socket.join(room);
  socket.emit('created', room, socket.id);
} else if (numClients === 1) {
  socket.join(room);
  socket.emit('joined', room, socket.id);
  io.sockets.in(room).emit('ready');
} else { // max two clients
  socket.emit('full', room);
}

Tu app de WebRTC simple permite que un máximo de dos apps similares compartan una sala.

HTML y JavaScript

  1. Actualiza index.html para que se vea de la siguiente manera:
<!DOCTYPE html>
<html>

<head>

  <title>Real-time communication with WebRTC</title>

  <link rel="stylesheet" href="css/main.css" />

</head>

<body>

  <h1>Real-time communication with WebRTC</h1>

  <script src="/socket.io/socket.io.js"></script>
  <script src="js/main.js"></script>
  
</body>

</html>

No verás nada en la página con este paso. Todos los registros se realizan en la consola del navegador. Para ver la consola en Chrome, presiona Control+Shift+J (o Command+Option+J en Mac).

  1. Reemplaza js/main.js por lo siguiente:
'use strict';

var isInitiator;

window.room = prompt("Enter room name:");

var socket = io.connect();

if (room !== "") {
  console.log('Message from client: Asking to join room ' + room);
  socket.emit('create or join', room);
}

socket.on('created', function(room, clientId) {
  isInitiator = true;
});

socket.on('full', function(room) {
  console.log('Message from client: Room ' + room + ' is full :^(');
});

socket.on('ipaddr', function(ipaddr) {
  console.log('Message from client: Server IP address is ' + ipaddr);
});

socket.on('joined', function(room, clientId) {
  isInitiator = false;
});

socket.on('log', function(array) {
  console.log.apply(console, array);
});

Configura el archivo socket.IO para que se ejecute en Node.js

En el archivo HTML, es posible que haya visto que está usando un archivo socket.IO:

<script src="/socket.io/socket.io.js"></script>
  1. En el nivel superior de tu directorio de work, crea un archivo llamado package.json con el siguiente contenido:
{
  "name": "webrtc-codelab",
  "version": "0.0.1",
  "description": "WebRTC codelab",
  "dependencies": {
    "node-static": "^0.7.10",
    "socket.io": "^1.2.0"
  }
}

Este es un manifiesto de la app que le indica a Node Package Manager (npm) qué proyecto

dependencias para instalar.

  1. Para instalar dependencias, como /socket.io/socket.io.js, ejecuta lo siguiente desde la terminal de línea de comandos en el directorio work:
npm install

Deberías ver un registro de instalación que finaliza con algo como lo siguiente:

3ab06b7bcc7666b9.png

Como puedes ver, npm instaló las dependencias definidas en package.json.

  1. Crea un archivo nuevo index.js en el nivel superior del directorio work (no en el directorio js) y agrega el siguiente código:
'use strict';

var os = require('os');
var nodeStatic = require('node-static');
var http = require('http');
var socketIO = require('socket.io');

var fileServer = new(nodeStatic.Server)();
var app = http.createServer(function(req, res) {
  fileServer.serve(req, res);
}).listen(8080);

var io = socketIO.listen(app);
io.sockets.on('connection', function(socket) {

  // Convenience function to log server messages on the client
  function log() {
    var array = ['Message from server:'];
    array.push.apply(array, arguments);
    socket.emit('log', array);
  }

  socket.on('message', function(message) {
    log('Client said: ', message);
    // For a real app, would be room-only (not broadcast)
    socket.broadcast.emit('message', message);
  });

  socket.on('create or join', function(room) {
    log('Received request to create or join room ' + room);

    var clientsInRoom = io.sockets.adapter.rooms[room];
    var numClients = clientsInRoom ? Object.keys(clientsInRoom.sockets).length : 0;

    log('Room ' + room + ' now has ' + numClients + ' client(s)');

    if (numClients === 0) {
      socket.join(room);
      log('Client ID ' + socket.id + ' created room ' + room);
      socket.emit('created', room, socket.id);

    } else if (numClients === 1) {
      log('Client ID ' + socket.id + ' joined room ' + room);
      io.sockets.in(room).emit('join', room);
      socket.join(room);
      socket.emit('joined', room, socket.id);
      io.sockets.in(room).emit('ready');
    } else { // max two clients
      socket.emit('full', room);
    }
  });

  socket.on('ipaddr', function() {
    var ifaces = os.networkInterfaces();
    for (var dev in ifaces) {
      ifaces[dev].forEach(function(details) {
        if (details.family === 'IPv4' && details.address !== '127.0.0.1') {
          socket.emit('ipaddr', details.address);
        }
      });
    }
  });

});
  1. Desde la terminal de línea de comandos, ejecuta el siguiente comando en el directorio work:
node index.js
  1. En el navegador, ve a http://localhost:8080.

Cada vez que navegues a esta URL, se te pedirá que ingreses un nombre para la sala.

Para unirte a la misma sala, ingresa el mismo nombre cada vez, como foo.

  1. Abre una nueva pestaña y vuelve a navegar a http://localhost:8080; luego, vuelve a ingresar el mismo nombre de habitación.
  2. Abre otra pestaña nueva, navega nuevamente a http://localhost:8080 y vuelve a ingresar el mismo nombre de habitación.
  3. Consulta la consola en cada una de las pestañas.

Deberías ver el registro de JavaScript.

Obtén puntos adicionales

  • ¿Qué mecanismos de mensajería alternativos podrían funcionar? ¿Qué problemas puedes encontrar con WebSocket puro?
  • ¿Qué problemas podrían estar relacionados con el escalamiento de esta app? ¿Puedes desarrollar un método para probar miles o millones de solicitudes de salas simultáneas?
  • Esta app usa un mensaje de JavaScript para obtener el nombre de una habitación. Descubre cómo obtener el nombre de la sala a partir de la URL. Por ejemplo, http://localhost:8080/foo asignaría el nombre foo a la habitación.

Más información

7. Combina las señales y la conexión del par

Encontrarás una versión completa de este paso en la carpeta step-05.

Reemplazar HTML y JavaScript

  1. Reemplaza el contenido de index.html por lo siguiente:
<!DOCTYPE html>
<html>

<head>

  <title>Real-time communication with WebRTC</title>

  <link rel="stylesheet" href="/css/main.css" />

</head>

<body>

  <h1>Real-time communication with WebRTC</h1>

  <div id="videos">
    <video id="localVideo" autoplay muted></video>
    <video id="remoteVideo" autoplay></video>
  </div>

  <script src="/socket.io/socket.io.js"></script>
  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="js/main.js"></script>
  
</body>

</html>
  1. Reemplaza js/main.js por el contenido de step-05/js/main.js.

Ejecuta el servidor de Node.js

Si no sigues este codelab desde tu directorio work, es posible que debas instalar las dependencias de la carpeta step-05 o tu carpeta de trabajo actual.

  1. Ejecuta el siguiente comando desde tu directorio de trabajo:
npm install
  1. Una vez instalado, si el servidor Node.js no se está ejecutando, inícialo con el siguiente comando en el directorio work:
node index.js

Asegúrate de usar la versión de index.js del paso anterior que implementa socket.IO. Para obtener más información sobre E/S de Node.js, consulta la sección Configura un servicio de señalización para intercambiar mensajes.

  1. En el navegador, ve a http://localhost:8080.
  2. Abre una nueva pestaña y vuelve a navegar a http://localhost:8080.

Un elemento video muestra la transmisión local de getUserMedia(), mientras que el otro muestra el video remoto transmitido a través de RTCPeerconnection.

  1. Ver el registro en la consola del navegador

Puntuación****en puntos adicionales

  • Esta app solo admite videollamadas individuales. ¿Cómo puedes cambiar el diseño para permitir que más de una persona comparta la misma sala de videochat?
  • El ejemplo tiene el nombre de la habitación foo hard-coded. ¿Cuál es la mejor forma de habilitar otros nombres de habitaciones?
  • ¿Cómo compartirían los usuarios el nombre de la sala? Intenta crear una alternativa a compartir los nombres de las salas.
  • ¿Cómo podrías cambiar la app?

Sugerencias

  • Consulta las estadísticas y los datos de depuración de WebRTC en chrome://webrtc-internals.
  • Usa el solucionador de problemas de webhook para verificar tu entorno local y probar la cámara y el micrófono.
  • Si tienes problemas con el almacenamiento en caché, prueba lo siguiente:
  1. Presiona Control y haz clic en Volver a cargar esta página.
  2. Reinicia el navegador.
  3. Ejecuta npm cache clean desde la línea de comandos.

8. Toma una foto y compártela mediante un canal de datos

Encontrarás una versión completa de este paso en la carpeta step-06.

Cómo funciona

Anteriormente, aprendiste a intercambiar mensajes de texto con RTCDataChannel. Este paso permite compartir archivos completos. En este ejemplo, las fotos se capturan con getUserMedia().

Las partes principales de este paso son las siguientes:

  1. Establezca un canal de datos.

En este paso, no agregarás ninguna transmisión de contenido multimedia a la conexión de par.

  1. Captura la transmisión de video de la cámara web con getUserMedia():
var video = document.getElementById('video');

function grabWebCamVideo() {
  console.log('Getting user media (video) ...');
  navigator.mediaDevices.getUserMedia({
    video: true
  })
  .then(gotStream)
  .catch(function(e) {
    alert('getUserMedia() error: ' + e.name);
  });
}
  1. Haz clic en Snap para obtener una instantánea (un fotograma) de la transmisión de video por Internet y mostrarla en un elemento canvas:
var photo = document.getElementById('photo');
var photoContext = photo.getContext('2d');

function snapPhoto() {
  photoContext.drawImage(video, 0, 0, photo.width, photo.height);
  show(photo, sendBtn);
}
  1. Haz clic en Enviar para convertir la imagen en bytes y enviarla por medio de un canal de datos:
function sendPhoto() {
  // Split the data-channel message in chunks of this byte length.
  var CHUNK_LEN = 64000;
  var img = photoContext.getImageData(0, 0, photoContextW, photoContextH),
    len = img.data.byteLength,
    n = len / CHUNK_LEN | 0;

  console.log('Sending a total of ' + len + ' byte(s)');
  dataChannel.send(len);

  // Split the photo and send in chunks of approximately 64KB.
  for (var i = 0; i < n; i++) {
    var start = i * CHUNK_LEN,
      end = (i + 1) * CHUNK_LEN;
    console.log(start + ' - ' + (end - 1));
    dataChannel.send(img.data.subarray(start, end));
  }

  // Send the reminder, if applicable.
  if (len % CHUNK_LEN) {
    console.log('last ' + len % CHUNK_LEN + ' byte(s)');
    dataChannel.send(img.data.subarray(n * CHUNK_LEN));
  }
}

El lado de recepción convierte los bytes de mensaje de canal de datos en una imagen y le muestra la imagen al usuario:

function receiveDataChromeFactory() {
  var buf, count;

  return function onmessage(event) {
    if (typeof event.data === 'string') {
      buf = window.buf = new Uint8ClampedArray(parseInt(event.data));
      count = 0;
      console.log('Expecting a total of ' + buf.byteLength + ' bytes');
      return;
    }

    var data = new Uint8ClampedArray(event.data);
    buf.set(data, count);

    count += data.byteLength;
    console.log('count: ' + count);

    if (count === buf.byteLength) {
      // we're done: all data chunks have been received
      console.log('Done. Rendering photo.');
      renderPhoto(buf);
    }
  };
}

function renderPhoto(data) {
  var canvas = document.createElement('canvas');
  canvas.width = photoContextW;
  canvas.height = photoContextH;
  canvas.classList.add('incomingPhoto');
  // The trail is the element that holds the incoming images.
  trail.insertBefore(canvas, trail.firstChild);

  var context = canvas.getContext('2d');
  var img = context.createImageData(photoContextW, photoContextH);
  img.data.set(data);
  context.putImageData(img, 0, 0);
}

Obtén el código

  1. Reemplaza el contenido de tu carpeta work con el contenido de step-06.

Tu archivo index.html de work debería verse de la siguiente manera****

<!DOCTYPE html>
<html>

<head>

  <title>Real-time communication with WebRTC</title>

  <link rel="stylesheet" href="/css/main.css" />

</head>

<body>

  <h1>Real-time communication with WebRTC</h1>

  <h2>
    <span>Room URL: </span><span id="url">...</span>
  </h2>

  <div id="videoCanvas">
    <video id="camera" autoplay></video>
    <canvas id="photo"></canvas>
  </div>

  <div id="buttons">
    <button id="snap">Snap</button><span> then </span><button id="send">Send</button>
    <span> or </span>
    <button id="snapAndSend">Snap &amp; Send</button>
  </div>

  <div id="incoming">
    <h2>Incoming photos</h2>
    <div id="trail"></div>
  </div>

  <script src="/socket.io/socket.io.js"></script>
  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="js/main.js"></script>

</body>

</html>
  1. Si no sigues este codelab desde tu directorio work, es posible que debas instalar las dependencias de la carpeta step-06 o tu carpeta de trabajo actual. Simplemente ejecute el siguiente comando desde su directorio de trabajo:
npm install
  1. Una vez instalado, si el servidor Node.js no se está ejecutando, inícialo con el siguiente comando desde tu directorio work:
node index.js
    Make sure that you're using the version of `index.js` that implements Socket.IO and 

Recuerda reiniciar tu servidor de Node.js si realizas algún cambio.

Para obtener más información sobre Node.js y socket, revisa la sección Configura una señalización

servicio para intercambiar mensajes.

  1. Si es necesario, haz clic en Permitir para permitir que la aplicación use la cámara web.

La app crea un ID de sala aleatorio y lo agrega a la URL.

  1. Abre la URL de la barra de direcciones en una nueva pestaña o ventana del navegador.
  2. Haz clic en Snap &Send y, luego, en Fotos entrantes en la otra pestaña de la parte inferior de la página.

La app transfiere fotos entre las pestañas.

Debería ver algo como esto:

911b40f36ba6ba8.png

Obtén puntos adicionales

¿Cómo puedes cambiar el código para que sea posible compartir cualquier tipo de archivo?

Más información

9. Felicitaciones

Creaste una app para el intercambio de datos y la transmisión de video en tiempo real.

Más información