Blog

Jak wdrożyć wideokonferencje w Next.js

· Andrij Tkachenko · 8 min czytania

Okno przeglądarki ze spotkaniem wideo, pięć osób i udostępniony ekran w siatce, pod nimi przyciski rozmowy, a obok lista współpracowników, trzy osoby oznaczone jako online

Spotkanie wideo wygląda jak jedna funkcja, a w aplikacji Next.js to kilka. Zespół otwiera swój pokój spotkań i wszyscy się widzą i słyszą, kilka osób albo kilkadziesiąt, z udostępnionymi ekranami. Ktoś prowadzi spotkanie i może wyciszyć hałasujący mikrofon albo kogoś usunąć. Współpracownik, który otworzy stronę w trakcie spotkania, powinien od razu na nim być, a reszta zespołu powinna dowiedzieć się, że się zaczęło, na dowolnej stronie, na której akurat jest.

Ten wpis omawia każdą część, której potrzebuje prawdziwa aplikacja, na pełnym, działającym przykładzie: Team Space, intranecie firmowym z naszego przewodnika po Next.js, gdzie każdy zespół ma swój pokój spotkań i swoje kanały, a współpracownicy widzą, kto jest online, i dzwonią do siebie. Możesz wypróbować go na żywo: bądź Mią, która prowadzi zespół Design, i rozpocznij jego spotkanie, a potem otwórz stronę Design w oknie prywatnym jako Ava, która też należy do Design. Będzie na spotkaniu, gdy tylko strona się otworzy.

Czego potrzebują wideokonferencje

  • Wiele kamer naraz. Między dwiema osobami obraz może iść prosto z jednej przeglądarki do drugiej. Na spotkaniu każda przeglądarka wysyła obraz z kamery raz, do serwerów mediów, które przekazują go wszystkim pozostałym (jak działają serwery mediów). Ich połączenia są otwarte przez całe spotkanie, a tego funkcja serverless nie utrzyma (dlaczego i co z tym zrobić).
  • Kto może dołączyć, a kto prowadzi. Reguła na Twoim serwerze, z Twoimi danymi: członkowie zespołu dołączają, jego lider jest gospodarzem, a nikt inny nie wchodzi.
  • Pokój spotkań na stronie. Kamera i ekran każdego uczestnika, przyciski sterowania i spotkanie, które trwa dalej, gdy ludzie przechodzą między stronami Twojej aplikacji.
  • Dołączanie. Przycisk, który rozpoczyna spotkanie albo do niego dołącza, i strona, która od razu wprowadza Cię na trwające spotkanie.
  • Narzędzia gospodarza. Wyciszenie kogoś albo usunięcie go ze spotkania.
  • Wiedza, że się zaczęło. Powiadomienie dla zespołu i listy trwających spotkań, które aktualizują się same.

Każdą z tych części możesz zbudować sam, na WebRTC i własnych serwerach mediów. Albo oddajesz media i ekran spotkania chmurze czasu rzeczywistego, a Twój serwer zachowuje jedyną część, która naprawdę należy do Ciebie: regułę. Tak działa Team Space, z WordPlus Cloud.

Przykład: intranet firmowy

Ludzie pracują tu w zespołach, a każdy zespół ma swojego lidera. Zespół ma własną stronę: swój pokój spotkań, swoje kanały i listę członków, najpierw tych, którzy są online. To aplikacja Next.js z App Routerem, Better Auth i Postgresem, a jej część czasu rzeczywistego to kilka plików. Pięć kroków tworzy jej spotkania.

1. Kto może dołączyć, a kto jest gospodarzem

Każdy zespół ma pokój spotkań, czyli rozmowę grupową nazwaną od numeru zespołu: meeting:3. Serwis deklaruje je raz, według tego wzorca, razem z regułą:

lib/realtime.ts
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;
});

Członek zespołu wchodzi, a lider zespołu z admin: true, co czyni go gospodarzem. Wszyscy inni dostają false, a strona zespołu pyta to samo isMember, więc strona i spotkanie nigdy się nie rozjeżdżają. Gdy strona chce dołączyć, najpierw pyta własny serwer serwisu, pod /api/realtime/calls: to ten sam route handler, przez który przechodzą rozmowy serwisu randkowego (jak wdrożyć rozmowy wideo w Next.js). Reguła działa z sesją serwisu, a strona dostaje przepustkę na spotkanie, podpisaną sekretem aplikacji, albo powód odmowy.

2. Pokój spotkań na stronie

Spotkanie pojawia się na samej stronie zespołu, w miejscu, które wskaże strona, z przyciskiem do jego rozpoczęcia w środku:

app/(app)/teams/[id]/meeting.tsx
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>
    );
}

Przed spotkaniem CallSpot pokazuje to, co ma w środku: nazwę pokoju, osoby, które są teraz na spotkaniu, i przycisk. Gdy ten współpracownik dołączy, w tym miejscu pojawia się spotkanie: kamery wszystkich w siatce, udostępniony ekran w dużym rozmiarze i przyciski mikrofonu, kamery, ekranu i wyjścia. Na innej stronie aplikacji spotkanie trwa dalej w okienku w rogu i wraca tutaj, gdy strona zespołu znów się otworzy. Przed opuszczeniem serwisu albo przeładowaniem strony pojawia się pytanie.

3. Prosto na spotkanie, gdy strona się otwiera

Współpracownik, który otwiera stronę w trakcie spotkania, nie powinien musieć niczego naciskać. Strona wie od swojego serwera, czy ktoś jest na spotkaniu (krok 4), i tylko wtedy przekazuje komponentowi join:

app/(app)/teams/[id]/meeting.tsx
const [join] = useState(people.length > 0 ? ('video' as const) : undefined);

<CallSpot rt={rt} call={room} join={join} className="h-[32rem] rounded-2xl">

Z join komponent dołącza do spotkania, gdy strona się otwiera, tak, jak zrobiłby to przycisk, a reguła decyduje tak samo jak przy kliknięciu. Decyzja zapada raz, przy otwarciu strony, i zostaje zachowana w stanie: kto wyjdzie ze spotkania albo kogo lider usunie, pozostaje poza nim, dopóki strona jest otwarta. Gdy na spotkaniu nikogo nie ma, strona do niczego nie dołącza, a pierwsza osoba rozpoczyna je przyciskiem.

Współpracownik dołącza z wyłączoną kamerą i wyłączonym mikrofonem: od razu widzi i słyszy spotkanie, a przeglądarka nie prosi o dostęp do żadnego z nich, dopóki sam nie włączy któregoś przyciskami sterowania, więc otwarcie strony nigdy nie uruchamia kamery znienacka. Tam, gdzie ludzie mają mówić od razu po wejściu, joinWith="microphone" włącza mikrofon już przy dołączaniu, a "microphone camera" jedno i drugie.

4. Kto jest na spotkaniu

WordPlus Cloud wie, kto jest na każdym spotkaniu, więc serwis nie prowadzi do tego żadnej tabeli:

lib/realtime.ts
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)] : []));
}

Strona zespołu pyta o to podczas renderowania i pokazuje twarze osób na spotkaniu z napisem „Mia and Omar are in the meeting”. Lista zespołów w ten sam sposób pyta realtime.listCalls() o wszystkie trwające spotkania i o to, ile osób jest na każdym.

5. Początek spotkania i kto się o nim dowiaduje

Gdy ktoś wchodzi na spotkanie, WordPlus Cloud wysyła do serwisu webhook. Serwis informuje każdą stronę, że spotkania się zmieniły, a gdy to pierwsza osoba na spotkaniu, informuje też resztę zespołu:

lib/realtime.ts
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() dociera do każdej strony, którą każda z tych osób ma otwartą, na dowolnym urządzeniu. W layoucie strony odbierają jedno i drugie:

app/(app)/live.tsx
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() });

Pierwsze pokazuje powiadomienie, które otwiera stronę zespołu: „Leo started the Engineering meeting”. Drugie sprawia, że każda strona ponownie pyta serwer o swoje listy, bez przeładowania, więc to, kto jest na każdym spotkaniu, zawsze zgadza się ze stanem faktycznym. Webhook group.left też wysyła changed, gdy ktoś wychodzi.

Gospodarz bez Twojego kodu

admin: true w regule to wszystko, czego potrzebuje gospodarz. Na ekranie lidera każdy kafelek pozostałych osób ma przyciski wyciszenia i usunięcia, a ekran rozmowy przekazuje je do WordPlus Cloud przez tę samą trasę, która przyjmuje je tylko od gospodarza. Wyciszony współpracownik może znów włączyć mikrofon, a usunięta osoba od razu znika ze spotkania. Żeby nie mogła wrócić, reguła odpowiada „nie”.

Co jest w zestawie

  • Udostępnianie ekranu, z dźwiękiem, prosto z przycisków sterowania.
  • Także rozmowy we dwoje. W Team Space czat ze współpracownikiem ma własną rozmowę, która pokazuje się obok wiadomości, tak jak w serwisie randkowym.
  • Imiona i twarze z Twojej aplikacji. Każdy kafelek pokazuje imię i zdjęcie współpracownika tak, jak podaje je profile serwisu, bez listy użytkowników przechowywanej w WordPlus Cloud.
  • Odbieranie dostępu. Gdy ktoś opuszcza zespół, serwis od razu usuwa go z trwającego spotkania zespołu przez realtime.call(name).remove(user), a od tej pory reguła odmawia.

Wypróbuj, a potem zbuduj własny

W demo na żywo współpracownicy i zespoły należą tylko do Ciebie przez dobę, więc nikt inny nie widzi, co tam piszesz ani z kim się spotykasz, a spotkanie samo kończy się po 10 minutach. Pod każdą stroną panel How this page works pokazuje kod, który ją tworzy, krok po kroku, z wartościami strony, którą masz otwartą.

Przewodnik po intranecie firmowym buduje cały serwis, z każdym plikiem jego części czasu rzeczywistego w całości, a rozmowy w Next.js pokazują wszystkie sposoby umieszczenia rozmowy albo spotkania we własnej aplikacji.

WordPlus Cloud for Apps ma cenę według jednoczesnych połączeń, od 1000 wzwyż, rozliczaną miesięcznie lub rocznie. Przy 1000 połączeń rozmowy grupowe nie są naliczane do 32 osób z wideo jednocześnie, przy czym osoba z samym dźwiękiem liczy się za pół, a każde kolejne 1000 połączeń dodaje 12. Powyżej każda dodatkowa osoba z wideo zużywa kredyt na minutę, a rozmowy między dwiema osobami nie zużywają kredytów. Zobacz rozmowy wideo dla aplikacji albo cennik dla aplikacji. Chcesz zapytać o swoją integrację? Napisz do nas.