Cómo implementar videoconferencias en Next.js

Una reunión por vídeo parece una sola función, y en una app de Next.js son varias. Un equipo abre su sala de reuniones y todos se ven y se oyen, unas pocas personas o unas cuantas decenas, con sus pantallas compartidas. Alguien la dirige y puede silenciar un micrófono ruidoso o sacar a alguien. Un compañero que abre la página mientras la reunión está en marcha debería entrar en ella al instante, y el resto del equipo debería enterarse de que ha empezado, esté en la página que esté.
Este artículo recorre cada parte que necesita una app real, con un ejemplo completo que funciona: Team Space, la intranet de equipo de nuestra guía de Next.js, donde cada equipo tiene su sala de reuniones y sus canales, y los compañeros ven quién está en línea y se llaman entre sí. Puedes probarlo en vivo: sé Mia, que dirige Design, e inicia su reunión, luego abre la página de Design en una ventana privada como Ava, también de Design, y estará en la reunión en cuanto se abra la página.
Lo que necesita una videoconferencia
- Muchas cámaras a la vez. Entre dos personas, la imagen puede ir directamente de un navegador al otro. En una reunión, cada navegador envía su cámara una sola vez a servidores de medios, que la reenvían a todos los demás (cómo funcionan los servidores de medios). Sus conexiones siguen abiertas durante toda la reunión, algo que una función serverless no puede mantener (por qué, y qué hacer).
- Quién puede unirse, y quién dirige. Una regla en tu servidor, con tus propios datos: los miembros del equipo se unen, su responsable es el anfitrión, y nadie más entra.
- Una sala de reuniones en la página. La cámara y la pantalla de todos, los controles, y una reunión que sigue mientras la gente pasa de una página de tu app a otra.
- Entrar. Un botón para iniciar la reunión o unirse a ella, y una página que te lleva directamente dentro mientras la reunión está en marcha.
- Los controles del anfitrión. Silenciar a alguien, o sacarlo.
- Saber que ha empezado. Un aviso al equipo, y listas de las reuniones en curso que se actualizan solas.
Puedes construir cada parte tú mismo, sobre WebRTC y servidores de medios propios. O dejas que una nube en tiempo real se encargue de los medios y de la pantalla de la reunión, mientras tu servidor conserva la única parte que es realmente tuya: la regla. Eso es lo que hace Team Space, con WordPlus Cloud.
El ejemplo: una intranet de equipo
Los compañeros trabajan en equipos, y cada equipo tiene un responsable. Un equipo tiene una página propia: su sala de reuniones, sus canales y quién forma parte del equipo, primero los que están en línea. Es una app de Next.js con App Router, Better Auth y Postgres, y su parte en tiempo real son unos pocos archivos. Cinco pasos hacen sus reuniones.
1. Quién puede unirse, y quién es el anfitrión
Cada equipo tiene una sala de reuniones, una llamada en grupo que lleva por nombre el número del equipo: meeting:3. El sitio las declara una sola vez, con ese patrón, junto con la regla:
realtime.groupCalls('meeting:{id}', async (user, { id }) => {
if (!(await isMember(id, user))) return false;
const team = await teamOf(id);
return team?.lead === String(user) ? { admin: true } : true;
});Un miembro del equipo entra, y su responsable con admin: true, que lo convierte en anfitrión. Cualquier otro recibe false, y la página del equipo pregunta al mismo isMember, así que la página y la reunión nunca discrepan. Cuando una página pide unirse, pregunta primero al servidor del propio sitio, en /api/realtime/calls: el mismo route handler por el que pasan las llamadas del sitio de citas (cómo implementar videollamadas en Next.js). La regla se ejecuta con la sesión del propio sitio, y la página recibe un pase para la reunión, firmado con el secreto de la app, o el motivo por el que no.
2. La sala de reuniones en la página
La reunión se muestra en la propia página del equipo, en el lugar donde la página la pone, con el botón para iniciarla dentro:
export function MeetingRoom({ room, team, people }: Props) {
const rt = useRealtime();
return (
<CallSpot rt={rt} call={room} className="h-[32rem] rounded-2xl">
<p>The {team} meeting room</p>
<GroupCallButton rt={rt} room={room} type="video" title={`${team} meeting`}>
{people.length > 0 ? 'Join the meeting' : 'Start the meeting'}
</GroupCallButton>
</CallSpot>
);
}Antes de la reunión, CallSpot muestra lo que lleva dentro: el nombre de la sala, quién está ahora en la reunión, y el botón. En cuanto este compañero entra, la reunión ocupa su lugar: la cámara de todos en una cuadrícula, una pantalla compartida en grande, y los controles del micrófono, la cámara, la pantalla y para salir. En otra página de la app, la reunión sigue en una ventana en la esquina, y vuelve aquí cuando se abre de nuevo la página del equipo. Salir del sitio, o recargar la página, pide confirmación antes.
3. Dentro al instante, al abrir la página
Un compañero que abre la página mientras la reunión está en marcha no debería tener que pulsar nada. La página sabe por su servidor si hay alguien en la reunión (paso 4), y solo entonces le pasa join al componente:
const [join] = useState(people.length > 0 ? ('video' as const) : undefined);
<CallSpot rt={rt} call={room} join={join} className="h-[32rem] rounded-2xl">Con join, el componente se une a la reunión en cuanto se abre la página, como lo haría el botón, y la regla decide igual que con un clic. Se decide una sola vez, al abrirse la página, y se guarda en el estado: quien sale de la reunión, o a quien el responsable saca, se queda fuera mientras la página siga abierta. Si no hay nadie en la reunión, la página no se une a nada, y el primero en entrar la inicia con el botón.
El compañero entra con la cámara y el micrófono apagados: ve y oye la reunión al instante, y el navegador no pide permiso para ninguno de los dos hasta que active uno desde los controles, así que abrir una página nunca enciende una cámara por sorpresa. Donde la gente deba hablar nada más entrar, joinWith="microphone" hace que cada uno entre con el micrófono ya encendido, y "microphone camera", con los dos.
4. Quién está dentro
WordPlus Cloud sabe quién está en cada reunión, así que el sitio no guarda ninguna tabla para ello:
export async function inMeeting(team: string): Promise<string[]> {
const { list } = await realtime.call(meetingOf(team)).get();
return list.flatMap((person) => (person.user ? [String(person.user)] : []));
}La página del equipo lo pregunta mientras se dibuja, y muestra las caras de quienes están en la reunión con «Mia and Omar are in the meeting». La lista de equipos pregunta a realtime.listCalls() de la misma forma, por cada reunión en curso y cuántos hay en cada una.
5. Una reunión que empieza, y quién se entera
Cuando alguien entra en una reunión, WordPlus Cloud envía al sitio un webhook. El sitio avisa a cada página de que las reuniones han cambiado, y cuando es el primero en entrar, avisa al resto del equipo:
realtime.on('group.joined', async ({ call, user }) => {
const team = teamIn(call);
if (!team) return;
await realtime.to('meetings').emit('changed');
// The first one in starts it.
const { people } = await realtime.call(call).get();
if (people > 1) return;
const others = (await peopleIn(team)).map((person) => person.id).filter((id) => id !== String(user));
await realtime.toUser(others).emit('meeting', {
team,
name: (await teamOf(team))?.name,
by: await nameOf(String(user)),
});
});toUser() llega a cada página que cada uno de ellos tenga abierta, en cualquier dispositivo. En el layout, las páginas escuchan los dos:
useUserEvent('meeting', (data: MeetingStarted) => {
if (path === `/teams/${data.team}`) return; // its page shows it already
show(`${data.by} started the ${data.name} meeting`, `/teams/${data.team}`);
});
useRoom('meetings', { changed: () => router.refresh() });El primero muestra un aviso que abre la página del equipo: «Leo started the Engineering meeting». El segundo hace que cada página vuelva a pedir sus listas al servidor, sin recargar, así que quién está en cada reunión se mantiene al día. El webhook group.left también envía changed cuando alguien sale.
El anfitrión, sin código tuyo
admin: true en la regla es todo lo que necesita el anfitrión. En la pantalla del responsable, cada recuadro de los demás tiene silenciar y expulsar, y la pantalla de llamada se lo pide a WordPlus Cloud por la misma ruta, que solo acepta esas acciones de un anfitrión. Un compañero silenciado puede volver a activar su micrófono, y quien es expulsado queda fuera al instante. Para mantenerlo fuera, la regla dice que no.
Lo que viene incluido
- Compartir pantalla, con su sonido, desde los controles.
- También llamadas entre dos. En Team Space, una conversación con un compañero tiene su propia llamada, que se muestra junto a los mensajes, como las del sitio de citas.
- Nombres y caras de tu app. Cada recuadro muestra el nombre y la foto de un compañero tal como los da el
profiledel sitio, sin ninguna lista de usuarios guardada en WordPlus Cloud. - Retirar el acceso. Cuando alguien deja un equipo, el sitio lo saca de su reunión en ese mismo momento con
realtime.call(name).remove(user), y desde entonces la regla dice que no.
Pruébalo y luego construye el tuyo
En la demo en vivo, los compañeros y los equipos son solo tuyos durante un día, así que nadie más ve lo que escribas ni con quién te reúnas, y una reunión termina sola a los 10 minutos. 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 de la intranet de equipo 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 poner una llamada o una reunión 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. Con 1.000 conexiones, las llamadas en grupo no se miden hasta 32 personas con vídeo a la vez, contando como media a una persona solo con audio, y cada 1.000 conexiones más añaden 12. Por encima, cada persona adicional con vídeo gasta un crédito por minuto, y las llamadas entre dos personas no gastan ninguno. Descubre las videollamadas para apps, o consulta los precios para apps. ¿Preguntas sobre tu integración? Escríbenos.