Codelab de Firebase y WebRTC

1. Introducción

En este codelab, aprenderás a compilar una aplicación de videochat sencilla con la API de WebRTC en tu navegador y Cloud Firestore para la señalización. La aplicación se llama FirebaseRTC y funciona como un ejemplo simple que te enseñará los conceptos básicos de la compilación de aplicaciones habilitadas para WebRTC.

Qué aprenderás

  • Inicio de una videollamada en una aplicación web con WebRTC
  • Señales de la parte remota con Cloud Firestore

Requisitos

Antes de comenzar este codelab, asegúrate de tener lo siguiente:

  • npm que suele incluir Node.js: Se recomienda usar Node.js

2. Cómo crear y configurar un proyecto de Firebase

Cree un proyecto de Firebase

  1. En Firebase console, haz clic en Agregar proyecto y asígnale el nombre FirebaseRTC.

Recuerda el ID de tu proyecto de Firebase.

  1. Haz clic en Crear proyecto.

La aplicación que compilarás usa dos servicios de Firebase disponibles en la Web:

  • Cloud Firestore para guardar datos estructurados en la nube y recibir notificaciones instantáneas cuando se actualizan los datos
  • Firebase Hosting para alojar y entregar tus recursos estáticos

Para este codelab específico, ya configuraste Firebase Hosting en el proyecto que clonarás. Sin embargo, en Cloud Firestore, te guiaremos en la configuración y habilitación de los servicios mediante Firebase console.

Habilite Cloud Firestore

La app usa Cloud Firestore para guardar los mensajes y recibir nuevos mensajes.

Deberás habilitar Cloud Firestore:

  1. En el menú Desarrollo de Firebase console, haz clic en Base de datos.
  2. Haz clic en Crear base de datos en el panel de Cloud Firestore.
  3. Selecciona la opción Comenzar en modo de prueba y, luego, haz clic en Habilitar después de leer la renuncia de responsabilidad sobre las reglas de seguridad.

El modo de prueba garantiza que puedas escribir con libertad en la base de datos durante el desarrollo. Más adelante en este codelab, mejoraremos la seguridad de nuestra base de datos.

3. Obtén el código de muestra

Clona el repositorio de GitHub desde la línea de comandos:

git clone https://github.com/webrtc/FirebaseRTC

El código de muestra debe clonarse en el directorio FirebaseRTC. Asegúrate de que tu línea de comandos se ejecute desde este directorio de ahora en adelante:

cd FirebaseRTC

Cómo importar la app de inicio

Abre los archivos de FirebaseRTC en el editor y modifícalos según las instrucciones a continuación. Este directorio contiene el código de inicio para el codelab que consta de una app de WebRTC que aún no es funcional. Haremos que sea funcional durante todo este codelab.

4. Instala la interfaz de línea de comandos de Firebase

La interfaz de línea de comandos (CLI) de Firebase te permite entregar tu aplicación web de forma local y, luego, implementarla en Firebase Hosting.

  1. Ejecuta el siguiente comando de npm para instalar la CLI: sh npm -g install firebase-tools
  1. Ejecuta el siguiente comando para verificar que la CLI se haya instalado de manera correcta:sh firebase --version

Asegúrate de que la versión de Firebase CLI sea la 6.7.1 o posterior.

  1. Ejecuta el siguiente comando para autorizar Firebase CLI: sh firebase login

Configuraste la plantilla de app web a fin de extraer la configuración de tu app para Firebase Hosting desde el directorio y los archivos locales de tu app. Para hacerlo, debes asociar tu app con tu proyecto de Firebase.

  1. Ejecuta el siguiente comando para asociar tu app con el proyecto de Firebase: sh firebase use --add

  2. Cuando se te solicite, selecciona el ID del proyecto y, luego, asígnale un alias a tu proyecto de Firebase.

Un alias es útil si tienes varios entornos (producción, etapa de pruebas, etc.). Sin embargo, para este codelab, simplemente usemos el alias de default.

  1. Sigue las instrucciones restantes en la línea de comandos.

5. Ejecuta el servidor local

Ya puedes comenzar a trabajar en nuestra app. Ejecutemos la app de manera local.

  1. Ejecuta el siguiente comando de Firebase CLI: sh firebase serve --only hosting

  2. Tu línea de comandos debería mostrar la siguiente respuesta: hosting: Local server: http://localhost:5000

Usamos el emulador de Firebase Hosting para entregar contenido a nuestra app de manera local. La aplicación web debería estar disponible en http://localhost:5000.

  1. Abre tu app en http://localhost:5000.

Deberías ver una copia de FirebaseRTC que se conectó a tu proyecto de Firebase.

La app se conectó automáticamente a tu proyecto de Firebase.

6. Creando una nueva sala

En esta aplicación, cada sesión de videochat se llama sala. Para crear una sala, el usuario debe hacer clic en un botón de su aplicación. Esto generará un ID que la parte remota puede usar para unirse a la misma sala. El ID se usa como la clave en Cloud Firestore para cada sala.

Cada sala contendrá el RTCSessionDescriptions para la oferta y la respuesta, así como dos colecciones separadas con candidatos de ICE de cada parte.

Tu primera tarea es implementar el código faltante para crear una nueva habitación con la oferta inicial del emisor. Abre public/app.js, busca el comentario // Add code for creating a room here y agrega el siguiente código:

const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);

const roomWithOffer = {
    offer: {
        type: offer.type,
        sdp: offer.sdp
    }
}
const roomRef = await db.collection('rooms').add(roomWithOffer);
const roomId = roomRef.id;
document.querySelector('#currentRoom').innerText = `Current room is ${roomId} - You are the caller!`

La primera línea crea un elemento RTCSessionDescription que representará la oferta del llamador. Esto se configura como la descripción local y, por último, se escribe en el objeto de sala nuevo en Cloud Firestore.

A continuación, detectaremos los cambios en la base de datos y detectaremos cuándo se agregó una respuesta del destinatario.

roomRef.onSnapshot(async snapshot -> {
    console.log('Got updated room:', snapshot.data());
    const data = snapshot.data();
    if (!peerConnection.currentRemoteDescription && data.answer) {
        console.log('Set remote description: ', data.answer);
        const answer = new RTCSessionDescription(data.answer)
        await peerConnection.setRemoteDescription(answer);
    }
});

Esto esperará a que el destinatario escriba el RTCSessionDescription para la respuesta y lo establecerá como la descripción remota del llamador RTCPeerConnection.

7. Unirte a una sala

El siguiente paso es implementar la lógica para unirse a una sala existente. Para ello, el usuario debe hacer clic en el botón Unirse a la sala y, luego, ingresar el ID de la sala a unirse. Tu tarea es implementar la creación de RTCSessionDescription para la respuesta y actualizar la sala en la base de datos según corresponda.

const offer = roomSnapshot.data().offer;
await peerConnection.setRemoteDescription(offer);
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);

const roomWithAnswer = {
    answer: {
        type: answer.type,
        sdp: answer.sdp
    }
}
await roomRef.update(roomWithAnswer);

En el código anterior, comenzamos por extraer la oferta del llamador y crear un RTCSessionDescription que configuramos como descripción remota. A continuación, crearemos la respuesta, la estableceremos como la descripción local y actualizaremos la base de datos. La actualización de la base de datos activará la devolución de llamada onSnapshot en el lado del emisor, que, a su vez, establecerá la descripción remota en función de la respuesta del destinatario. De esta manera, se completa el intercambio de los objetos RTCSessionDescription entre el llamador y el destinatario.

8. Recopila candidatos de ICE

Antes de que el emisor y el destinatario puedan conectarse entre sí, también deben intercambiar candidatos de ICE que le indican a WebRTC cómo conectarse al par remoto. La siguiente tarea consiste en implementar el código que escucha a los candidatos de ICE y los agrega a una colección en la base de datos. Busca la función collectIceCandidates y agrega el siguiente código:

async function collectIceCandidates(roomRef, peerConnection,
                                    localName, remoteName) {
    const candidatesCollection = roomRef.collection(localName);

    peerConnection.addEventListener('icecandidate', event -> {
        if (event.candidate) {
            const json = event.candidate.toJSON();
            candidatesCollection.add(json);
        }
    });

    roomRef.collection(remoteName).onSnapshot(snapshot -> {
        snapshot.docChanges().forEach(change -> {
            if (change.type === "added") {
                const candidate = new RTCIceCandidate(change.doc.data());
                peerConnection.addIceCandidate(candidate);
            }
        });
    })
}

Esta función realiza dos tareas. Recopila candidatos de ICE desde la API de WebRTC, los agrega a la base de datos, detecta candidatos ICE agregados desde el par remoto y los agrega a su instancia de RTCPeerConnection. Es importante cuando se detectan cambios en la base de datos para filtrar cualquier elemento que no sea un agregado nuevo, ya que, de lo contrario, habríamos agregado el mismo conjunto de candidatos ICE una y otra vez.

9. Conclusión

En este codelab, aprendiste a implementar la señalización para WebRTC con Cloud Firestore y a crear una aplicación de videochat sencilla.

Para obtener más información, consulta los siguientes recursos:

  1. Código fuente de FirebaseRTC
  2. Muestras de WebRTC
  3. Cloud Firestore