Blog

Cómo implementar videollamadas en Next.js

· Andrij Tkachenko · 6 min de lectura

Dos ventanas de navegador una junto a otra, cada una con la otra persona en vídeo y un chat al lado, unidas por un corazón

Una videollamada parece una sola funcionalidad, pero en una app de Next.js son varias. Dos navegadores tienen que encontrarse y ponerse de acuerdo en cómo conectarse. La persona a la que llamas tiene que oír la llamada esté en la página que esté. Algunas redes nunca dejan pasar una conexión directa. Y antes de todo eso, tu app tiene que decidir quién puede llamar a quién.

La mayoría de los tutoriales se quedan en dos navegadores en una misma página que intercambian ofertas a mano. Este artículo recorre todas las piezas que necesita una app real, con un ejemplo completo que funciona: el sitio de citas de nuestra guía de Next.js, donde dos miembros tienen una cita por vídeo desde su chat. Puedes probarlo en vivo: elige quién quieres ser, luego ábrelo en una ventana privada, elige a otra persona y llama.

Lo que necesita una videollamada

  • Señalización y aviso de llamada. Antes de que haya imagen, los dos navegadores intercambian ofertas y candidatos de red a través de un servidor, y la llamada tiene que sonar en cada pestaña que tenga abierta la persona a la que se llama. Eso exige conexiones abiertas todo el tiempo, algo que una función serverless no puede mantener (por qué, y qué hacer al respecto).
  • Un camino entre los navegadores. Directo cuando sus redes lo permiten, y a través de un relé cuando no (los servidores STUN y TURN, explicados).
  • Quién puede llamar a quién. Una regla en tu servidor, con los datos de tu propia app, que se comprueba antes de que suene ninguna llamada.
  • La cámara y el micrófono. Que el navegador pida permiso una sola vez, y poder cambiar de dispositivo, silenciarse y desactivar la cámara.
  • Una pantalla de llamada. La llamada entrante, contestar y rechazar, los dos vídeos, la duración y colgar.
  • Servidores multimedia, en cuanto hay más de dos personas en una llamada (cómo funcionan las llamadas en grupo).

Puedes construir cada una de estas piezas tú mismo sobre WebRTC. O puedes dejar que una nube en tiempo real se encargue de las conexiones, del aviso de llamada y de los relés, mientras tu servidor se queda con la única parte que es realmente tuya: la regla. Eso es lo que hace el sitio de citas, con WordPlus Cloud.

El ejemplo: citas por vídeo en un sitio de citas

Los miembros del sitio encuentran a las personas que buscan, ven quién está en línea, chatean y tienen una cita por vídeo antes de conocerse en persona. Es una app de Next.js con el App Router y Postgres, y su parte en tiempo real ocupa unos pocos archivos. Sus llamadas se montan en cuatro pasos.

1. Quién puede llamar a quién

En un sitio de citas, dos personas pueden hablar cuando cada una es lo que la otra busca y ninguna ha bloqueado a la otra. El sitio define esa regla una sola vez, como canMeet, y cada chat es un número con sus miembros. mayChat dice que alguien puede usar un chat cuando es uno de sus miembros y todavía puede conocer a todos los demás. Las llamadas se declaran con el nombre de la sala del chat, con esa regla para las dos personas:

lib/realtime.ts
realtime.calls('chat:{id}', async (user, other, { id }) => {
    // The one asking: who calls, as they call, or who is called, as they answer.
    const userMayChat = await mayChat(user, id);
    // The other one of the call.
    const otherMayChat = await mayChat(other, id);
    return userMayChat && otherMayChat;
});

Se ejecuta para quien llama, y otra vez para quien recibe la llamada cuando contesta. Así, un bloqueo tiene efecto al instante: desde el momento en que uno bloquea al otro, canMeet dice que no, y la siguiente llamada se rechaza.

2. Dónde se ejecuta la regla

Cuando una página pide llamar, o contestar, primero pregunta al propio servidor del sitio, en un route handler:

app/api/realtime/calls/route.ts
import { currentUserId } from '@/lib/auth';
import { realtime } from '@/lib/realtime';

export async function POST(request: Request) {
    // JSON only, which a page of another site can't send here.
    if (!request.headers.get('content-type')?.includes('application/json')) {
        return new Response(null, { status: 415 });
    }
    const ask = await request.json();
    const user = await currentUserId();
    const pass = await realtime.authorizeCall(ask, user);
    return Response.json(pass);
}

currentUserId() es el inicio de sesión del propio sitio, así que la sesión funciona aquí igual que en cualquier página. Cuando la regla dice que sí, la página recibe un pase firmado con el secreto de la app, y con ese pase WordPlus Cloud hace sonar la llamada a la otra persona. La ruta responde en una sola petición corta, que es justo lo que el hosting serverless hace bien.

3. La llamada suena en todas las páginas

El layout de las páginas con sesión iniciada se conecta una vez y renderiza la pantalla de llamada junto a las páginas:

app/(app)/screens.tsx
'use client';

import '@wordplus/realtime-ui/calls.css';
import '@wordplus/realtime-ui/chat.css';
import { useRealtime } from '@wordplus/realtime/react';
import { CallScreen } from '@wordplus/realtime-ui/react';

export function Screens() {
    return <CallScreen rt={useRealtime()} />;
}

<Screens /> está dentro de RealtimeProvider en app/(app)/layout.tsx, así que una llamada suena en cualquier página que un miembro tenga abierta, en cada pestaña y en cada dispositivo, y deja de sonar en todas partes en cuanto se contesta en uno. La pantalla de llamada es la de Better Messages, dibujada en el propio árbol de React del sitio, así que el CSS del sitio también se le aplica.

4. La llamada en su chat

En un sitio de citas, la llamada va con la conversación. El chat de la página de un miembro lleva la llamada dentro, junto a los mensajes:

app/(app)/members/[id]/chat.tsx
<Chat
    rt={useRealtime()}
    room={room}
    to={to}
    name={name}
    avatar={avatar}
    call
    startCall={start}
/>

Su barra superior muestra a la otra persona con sus botones de llamada. El botón de vídeo de una tarjeta del catálogo abre el chat iniciando ya la llamada (startCall), así que la cita empieza en el mismo lugar donde los dos también pueden escribirse.

El chat es solo uno de los lugares donde puede ir una llamada. Un CallButton inicia una desde cualquier parte, un CallSpot muestra las llamadas en la zona de la página que elijas, y useCalls te da todas las llamadas para que dibujes tu propia pantalla.

Lo que viene incluido

  • Un solo permiso. El navegador pide la cámara y el micrófono juntos, una sola vez.
  • Siempre conecta. Una llamada entre dos personas va directamente de un navegador al otro cuando puede, a través de relés en los puertos que toda red deja abiertos cuando no puede, y a través de servidores multimedia cuando ni siquiera esos llegan, pasando de un camino a otro sin colgar, y cifrada en todos ellos.
  • No se corta. La conexión, y con ella la llamada, se mantiene mientras el miembro pasa de una página del sitio a otra.
  • Llamadas en grupo con la misma API. Una sala con más personas funciona sobre servidores multimedia, con su propio botón y su propia pantalla de llamada en grupo (videollamadas para apps).

Pruébalo y luego crea el tuyo

En la demo en vivo, las once personas son solo tuyas durante un día, así que nadie más ve lo que escribas ni a quién llames. Debajo de cada página, How this page works muestra el código que la hace funcionar, paso a paso, con los valores de la página que tienes abierta.

La guía del sitio de citas construye el sitio entero, con cada archivo de su parte en tiempo real completo, y las llamadas en Next.js reúne todas las formas de iniciar una llamada en tu propia app.

WordPlus Cloud for Apps tiene un precio según las conexiones simultáneas, a partir de 1.000, con facturación mensual o anual. Descubre lo que hace para apps, o consulta los precios para apps. ¿Preguntas sobre tu integración? Escríbenos.