https://a.storyblok.com/f/270183/58037/f4fd342640/breakoutroom.jpg

Crie uma aplicação de salas de discussão em JavaScript com a Video API da Vonage

Publicado em May 24, 2022

Tempo de leitura: 5 minutos

Este artigo foi escrito em colaboração com Yinping Ge

As salas de discussão são um recurso comum solicitado por muitos clientes, especialmente aqueles da área de educação. Geralmente, elas permitem “dividir a sala de reunião principal em salas separadas”, “distribuir os participantes por essas salas de discussão” e “permitir que os participantes enviem mensagens ao anfitrião, independentemente da sala em que estejam”, etc.

Com a Vonage Video API, há mais de uma maneira de implementar esse recurso de salas de discussão em seu aplicativo.

Uma maneira é criar uma grande sessão de Video com lógica que controle quais transmissões cada usuário deve acompanhar. Outra opção é “implementar salas de discussão como sessões separadas” e, em seguida, “conectar os participantes a essas diferentes sessões criadas para cada sala de discussão”.

Este tutorial explica como usar as sessões separadas para integrar o recurso “Breakout Room” ao nosso aplicativo de demonstração, que utiliza a API de sinalização para implementar o gerenciamento das salas de discussão e reutiliza o objeto Publisher ao alternar entre as salas

Espero que os gráficos a seguir possam lhe dar uma ideia geral, para começar. Inicialmente, todos os participantes se conectam à sessão da sala principal:

Graph showing all participants connect to the main-room’s session

Depois que o anfitrião clica no botão para criar salas de discussão, o servidor do aplicativo aciona a Video API do Vonage para criar uma sessão para cada sala de discussão e retorna esses IDs de sessão a cada participante.

Graph showing application connects participants to breakout rooms

Em seguida, o aplicativo conecta os participantes às sessões dessas salas de discussão, permitindo que eles escolham uma sala para participar ou distribuindo-os automaticamente por salas diferentes, dependendo da opção que o anfitrião tiver selecionado ao criar as salas de discussão. (O anfitrião pode escolher entre “Atribuir automaticamente” e “Deixar os participantes escolherem uma sala”).

Pré-requisitos

Um account da Video API da Vonage. Clique em “Inscrever-se” para criar uma, caso ainda não tenha uma. ReactJS versão >= 16.8 Node.js versão >= 16.13 PostgreSQL 14 como banco de dados; você pode escolher qualquer sistema de armazenamento de sua preferência

Você deve conseguir ver todas as dependências no repositório do GitHub e recomendamos que você sempre use a versão mais recente do SDK da Vonage. As versões listadas aqui foram as utilizadas quando estávamos trabalhando neste aplicativo de demonstração.

Projeto do servidor e do banco de dados do aplicativo

O servidor de aplicativos cria salas, inicia sessões, gera tokens, gerencia as salas e os participantes e envia mensagens de sinalização para as salas.

O servidor de aplicativos atua como um “repetidor”, utilizando a API Signaling-REST para transmitir mensagens entre diferentes salas/sessões em cenários como, por exemplo, quando um participante precisa levantar a mão para o anfitrião que está em uma sala diferente (ou seja, conectado a outra sessão), e para o recurso principal: o gerenciamento de salas de discussão em grupo. Explicaremos em detalhes mais adiante como usamos as mensagens de sinalização no gerenciamento dessas salas.

Ao executar o servidor de aplicativos, a tabela de salas será criada caso ainda não exista. Entendo que, na prática, a tabela de salas pode ser um pouco mais complexa. Aqui, listamos apenas os dados básicos de que precisamos. O script para criar a tabela de salas é o seguinte:

CREATE TABLE IF NOT EXISTS rooms(
    id VARCHAR(255) PRIMARY KEY,
    name VARCHAR(255) DEFAULT NULL,
    session_id VARCHAR(255) DEFAULT NULL,
    main_room_id VARCHAR(255) DEFAULT NULL,
    max_participants SMALLINT DEFAULT 0
)

O session_id armazena o ID de uma sessão associada à sala. O max_participants define o número máximo de participantes que a sala permite. O main_room_id diferencia se esta é uma sala secundária pertencente à sala principal ou apenas uma sala principal que pode ter salas secundárias: quando definido como NULL, trata-se de uma sala principal; caso contrário, é uma sala secundária e seu valor deve ser definido como o ID da sala principal a que está vinculada.

Inicialmente, na página de login, todos os usuários optam por entrar em uma sala, também conhecida como sala principal. Ao receber a solicitação do front-end, o servidor de aplicativos chama a Video API para criar uma sessão para essa sala principal e adiciona um registro à tabela de salas com session_id definido como o ID da sessão criada e main_room_id definido como NULL. Em seguida, ele retorna o session_id a todos os usuários conectados para que eles se conectem à sessão.

Quando a reunião está em andamento e um usuário anfitrião decide criar salas de discussão, após enviar as opções listadas em “Controle das salas de discussão”, tais como “quantas salas de discussão serão criadas”, “Permitir que os participantes escolham a sala” ou “Atribuir automaticamente”, etc., o front-end envia uma createSession solicitação com o parâmetro breakoutRooms contendo as seleções acima para o servidor de aplicativos, que então criará uma sessão para cada sala de discussão de acordo com as opções e armazenará o ID da sessão e outras informações na tabela de salas, com um registro para cada sala de discussão com main_room_id definido como o ID da sala principal.

Use a API de sinalização para implementar o gerenciamento de salas de discussão

O objeto `Room` contém as referências à sessão, à mensagem (`breakoutRoomSignal`) e aos participantes, e fornece os recursos para criar salas de discussão em grupo e gerenciar os participantes.

O aplicativo utiliza a API Signaling-REST para enviar mensagens aos clientes conectados a todas as sessões relacionadas a uma sala principal ou sala de discussão, informando sobre mudanças de sala, temporizador e solicitações de levantamento de mão, etc.

Por exemplo, uma mensagem de notificação com o tipo e os dados abaixo tem como objetivo informar aos usuários do aplicativo sobre a criação de novas salas de discussão, para que eles possam escolher uma para participar:

{
   "type": "signal:breakout-room",
   "data": {
       "message": "roomCreated (chooseroom)",
       "breakoutRooms": \[], //array of available rooms
   }
}
  • mensagem de notificação informando que “todos os quartos foram removidos”:

{
     "type": "signal:breakout-room",
     "data": {
         "message": "allRoomRemoved",
         "breakoutRooms": \[...],
     }
 }
  • informar a um participante que foi transferido de uma sala (de discussão em grupo) para outra:

{
   "type": "signal:breakout-room",
   "data": {
       "message": "'participantMoved'",
       "breakoutRooms": \[...],
   }
}
  • por outro lado, uma mensagem de sinalização com o tipo definido como signal:count-down-timer serve para informar sobre um temporizador:

{
   "type": "signal:count-down-timer",
   "data": {
       "period": 1,
   }
}

Para essas breakoutRoomSignal mensagens, o aplicativo realiza as ações correspondentes; por exemplo, no caso de participantMoved, ele transfere o participante para a sala designada.

if (mMessage.breakoutRoomSignal.message === 'participantMoved' && roomAssigned && (!currentRoomAssigned || currentRoomAssigned.id !== roomAssigned.id)) {
    setCurrentRoomAssigned(roomAssigned);
    mNotification.openNotification("Room assigned by Host/Co-host", `You will be redirected to Room: ${roomAssigned.name} in 5 seconds.`, () => handleChangeRoom(roomAssigned.name))
}

Ao handleChangeRoom, o aplicativo sairá da sala atual (desconectando-se da sessão associada a ela) e entrará na sala designada (conectando-se à sessão associada a ela).

async function handleChangeRoom(publisher, roomName) {
    const newRooms = \ [...mMessage.breakoutRooms];
    let targetRoom = newRooms.find((room) => room.name === roomName);

    await mSession.session.unpublish(publisher);
    await mSession.session.disconnect();

    const connectionSuccess = await connect(mSession.user, targetRoom ? targetRoom.id : '');

    if (!connectionSuccess) {
        // Force connect to main room;
        targetRoom = null;
        roomName = '';
        await connect(mSession.user);
    }

    let data = {
        fromRoom: currentRoom.name,
        toRoom: roomName ? roomName : mainRoom.name,
        participant: mSession.user.name
    }

    setInBreakoutRoom(targetRoom && targetRoom.name !== mainRoom.name ? targetRoom : null);
}

Reutilizar o objeto Publisher ao alternar entre salas

Quando um participante sai da sala principal e entra em uma sala de discussão (ou em outra situação semelhante), recomenda-se reutilizar o objeto Publisher para economizar recursos.

Para cada type": "signal:breakout-room mensagem que possa levar um cliente a sair de uma sala e entrar em outra, por exemplo, roomCreated (automatic), o que o aplicativo faz é se desconectar de uma sessão e, em seguida, se conectar a outra sessão. Durante esse processo, o fluxo publicado na sessão anterior será destruído e o evento streamDestroyed será disparado para o cliente editor. Para reter o objeto Publisher para reutilização, o método preventDefault do evento streamDestroyed deve ser chamado.

function handleStreamDestroyed(e) {
    if (e.stream.name !== "sharescreen") e.preventDefault();
    if (e.reason === 'forceUnpublished') {
        console.log('You are forceUnpublished');
        setStream({
            ...e.stream
        })
        setPublisher({
            ...e.stream.publisher
        })
    }
}

O aplicativo de demonstração reutiliza esse objeto Publisher para publicar na sessão associada à sala de discussão.

async function publish(
    user,
    extraData
) {
    try {
        if (!mSession.session) throw new Error("You are not connected to session");
        if (!publisher || publisherOptions.publishVideo !== hasVideo || publisherOptions.publishAudio !== hasAudio) {


            if (publisher) resetPublisher();
            const isScreenShare = extraData && extraData.videoSource === 'screen' ? true : false;
            const options = {
                insertMode: "append",
                name: user.name,
                publishAudio: isScreenShare ? true : hasVideo,
                publishVideo: isScreenShare ? true : hasAudio,
                style: {
                    buttonDisplayMode: "off",
                    nameDisplayMode: displayName ? "on" : "off"
                }
            };
            const finalOptions = Object.assign({}, options, extraData);
            setPublisherOptions(finalOptions);
            const newPublisher = OT.initPublisher(containerId, finalOptions);
            publishAttempt(newPublisher, 1, isScreenShare);
        } else {
            publishAttempt(publisher);
        }
    } catch (err) {
        console.log(err.stack);
    }
}

Conclusão

Ao utilizar a forma de criar sessões separadas para o recurso de salas de discussão, você só precisa se preocupar em conectar os participantes à sessão correta. Dê uma olhada em o código para obter mais detalhes e, esperamos, que você ache isso útil para sua aplicação de salas de discussão.

Compartilhar:

https://a.storyblok.com/f/270183/400x351/0c294bb1fc/iu-jie-lim.png
Iu Jie Lim

Iu Jie is a Software Engineer who is constantly seeking innovative ways to solve a problem. She is passionate about new technology, especially relating to cloud and AI. Out of work, she likes to spend her time hunting for tasty food with family.