https://a.storyblok.com/f/270183/37945/16c744dd5f/react-native_video-express_1200x600.png

Crie um aplicativo de vídeo para várias pessoas com o novo Vonage Video Express

Publicado em September 24, 2021

Tempo de leitura: 6 minutos

Este artigo foi escrito em colaboração com Javier Molina Sanz

Esta postagem no blog vai ajudar você a desenvolver uma aplicação de videoconferência com múltiplos participantes baseada no ReactJS e no novo Vonage Video Express. O Video Express oferece, por padrão, os seguintes recursos:

  • Gerenciador de salas e participantes: lógica simplificada de publicação, inscrição e gerenciamento de transmissões

  • Gerenciamento de layout: interface do usuário responsiva pronta para uso e gerenciador de layout com componentes personalizáveis

  • Otimização da qualidade do Video (taxa de quadros e resolução) com base no número de participantes, nos tamanhos de renderização, na CPU e nas condições da rede

  • Otimização de rede: Remova automaticamente o Video ou o áudio dos participantes que não estão visíveis ou não estão falando, otimizando os recursos de largura de banda.

  • Facilidade de uso: Oferece uma interação mais natural ao substituir publicação, assinatura e fluxos por “Sala” e “Participantes”.

Quer pular direto para o final? Você pode encontrar todo o código-fonte deste tutorial no GitHub.

Arquitetura do aplicativo

O aplicativo está dividido em duas seções principais: lado do servidor e lado do cliente: Lado do servidor: um servidor NodeJS simples, responsável pela geração de credenciais e pelo gerenciamento de arquivos. Lado do cliente: um SPA (aplicativo de página única) em React que utiliza React Hooks.

É no lado do cliente que tudo realmente acontece. Com o Video Express, conseguimos implementar um aplicativo de videoconferência multiparticipante responsivo e escalável, que cuida das otimizações que normalmente tomam muito tempo.

Cliente

O aplicativo React utiliza o módulo @vonage/video-express por meio do NPM. Lembre-se de que você também pode usar o Video Express por meio de uma tag de script HTML — confira a Documentação do Video Express para obter todos os detalhes.

O aplicativo é baseado nos React Hooks, que foram lançados com o React 16.8. A seguir, vamos dar uma olhada mais de perto nos principais hooks desse aplicativo.

UseRoom

O UseRoom é o que gerencia o ciclo de vida da nossa sala de vídeo. Graças ao Video Express, não precisamos gerenciar o ciclo de vida da Sessão, do Emissor e do Assinante. Em vez disso, basta instanciar um objeto Room e, em seguida, usar o room.join() , que, por sua vez, cuidará de tudo para nós nos bastidores.

Primeiro, precisamos criar uma função responsável por inicializar nosso objeto `Room` e participar da chamada. Precisamos fornecer nossa autenticação (apiKey, sessionId, e token) e outros parâmetros opcionais usados como configurações do editor, como o userName, o contêiner onde nosso Room ficará visível e algumas configurações opcionais do editor.

Como vamos usar o gerenciador de layout padrão fornecido pelo Video Express, estamos passando alguns parâmetros de layout: definir uma visualização em grade para o layout inicial e definir um elemento HTML personalizado para a visualização de compartilhamento de tela. Você pode encontrar a lista completa de parâmetros aqui.

const createCall = useCallback(
    (
      { apikey, sessionId, token },
      roomContainer,
      userName,
      publisherOptions
    ) => {
      if (!apikey || !sessionId || !token) {
        throw new Error('Check your credentials');
      }

      roomRef.current = new MP.Room({
        apiKey: apikey,
        sessionId: sessionId,
        token: token,
        roomContainer: 'roomContainer',
        participantName: userName,
        managedLayoutOptions: {
          layoutMode: 'grid',
          screenPublisherContainer: 'screenSharingContainer'
        }
      })
       startRoomListeners();

       roomRef.current
        .join({ publisherProperties: finalPublisherOptions })
        .then(() => {
          setConnected(true);
          setCamera(roomRef.current.camera);
          setScreen(roomRef.current.screen);
          addLocalParticipant({ room: roomRef.current });
        })
        .catch(e => console.log(e));
    },
    [ ]
  );

Assim que o Room objeto for inicializado, chamamos a startRoomListeners função para ativar os ouvintes de eventos no Room objeto. Em seguida, chamamos o room.join() método com alguns publisherSettings para participar da sessão. Precisamos dos ouvintes de eventos para sermos notificados sobre eventos como a entrada de um novo participante, a criação de uma nova transmissão de compartilhamento de tela, um usuário se reconectando à chamada, etc.

 
const startRoomListeners = () => {
    if (roomRef.current) {
      roomRef.current.on('connected', () => {
        console.log('Room: connected');
      });
      roomRef.current.on('disconnected', () => {
        setNetworkStatus('disconnected');
        console.log('Room: disconnected');
      });
      roomRef.current.camera.on('created', () => {
        setCameraPublishing(true);
        console.log('camera publishing now');
      });
      roomRef.current.on('reconnected', () => {
        setNetworkStatus('reconnected');
        console.log('Room: reconnected');
      });
      roomRef.current.on('reconnecting', () => {
        setNetworkStatus('reconnecting');
        console.log('Room: reconnecting');
      });
      roomRef.current.on('participantJoined', participant => {
        console.log(participant);
        addParticipants({ participant: participant });
        console.log('Room: participant joined: ', participant);
      });
      roomRef.current.on('participantLeft', (participant, reason) => {
        removeParticipants({ participant: participant });
        console.log('Room: participant left', participant, reason);
      });
    }
  };

Observe que também mantemos um registro dos participantes da sessão para podermos exibir uma lista deles. Vamos criar uma variável de estado que é atualizada sempre que um participante entra ou sai da sala.

Outro recurso útil implementado é o componente de status de rede. Esse recurso será útil quando o usuário se desconectar ou se reconectar para atualizar a interface do usuário, informando-o sobre eventuais problemas de rede.

UseDevices

Hoje em dia, é muito comum ter vários dispositivos de áudio e vídeo disponíveis. Alguns usuários preferem usar fones de ouvido, enquanto outros preferem conectar uma webcam externa ao computador. Em um aplicativo de Video, é essencial permitir que o usuário escolha entre diferentes dispositivos. O método useDevices explica como obter uma lista dos dispositivos disponíveis.

  useEffect(() => {
    navigator.mediaDevices.addEventListener('devicechange', getDevices);
    getDevices();

    return () => {
      navigator.mediaDevices.removeEventListener('devicechange', getDevices);
    };
  }, [getDevices]);

Configuramos um ouvinte de eventos para detectar alterações nos dispositivos de mídia; acionaremos nossa getDevices() função quando isso acontecer.

const getDevices = useCallback(async () => {
    if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
      console.log('enumerateDevices() not supported.');
      return;
    }
    try {
      const devices = await MP.getDevices();
      const audioInputDevices = devices.filter(
        (d) => d.kind.toLowerCase() === 'audioinput'
      );
      const audioOutputDevices = devices.filter(
        (d) => d.kind.toLowerCase() === 'audiooutput'
      );
      const videoInputDevices = devices.filter(
        (d) => d.kind.toLowerCase() === 'videoinput'
      );
      setDeviceInfo({
        audioInputDevices,
        videoInputDevices,
        audioOutputDevices
      });
      // });
    } catch (err) {
      console.log('[loadDevices] - ', err);
    }
  }, []);

Nossa getDevices() função chamará o MP.geDevices() , que retorna uma lista de dispositivos disponíveis assim que o usuário conceder acesso aos dispositivos. Em seguida, filtraremos os dispositivos e preencheremos nosso estado com os diferentes dispositivos disponíveis.

const [deviceInfo, setDeviceInfo] = useState({
    audioInputDevices: [],
    videoInputDevices: [],
    audioOutputDevices: []
  });

UsePreviewPublisher

O Video Express também ajuda você a implementar a experiência do usuário antes da chamada. De fato, o Video Express implementa um classe PreviewPublisher . A ideia da PreviewPublisher classe é permitir que os desenvolvedores visualizem a mídia com facilidade e garantam que seus dispositivos (áudio/Video) funcionem corretamente, sem a necessidade de criar um Room objeto.

Vamos criar uma pré-visualização para permitir que o usuário escolha o dispositivo correto (caso tenha vários), garantir que o microfone capte o áudio e que a câmera funcione corretamente. Confira a implementação completa no GitHub.

Primeiro, vamos obter os dispositivos disponíveis por meio do nosso hook UseDevices.

const { deviceInfo, getDevices } = useDevices();

Depois de inicializarmos o publisher de pré-visualização com o elemento de destino, chamamos o previewMedia método para visualizar a mídia. Também configuraremos alguns ouvintes de eventos para lidar com o acesso ao dispositivo e audioLevel eventos. Como você pode ver, não chamaremos a getDevices() função até que o usuário tenha concedido permissão aos dispositivos (ao ocorrer o accessAllowed evento)

const createPreview = useCallback(
    async (targetEl, publisherOptions) => {
      try {
        const publisherProperties = Object.assign({}, publisherOptions);
        console.log('[createPreview]', publisherProperties);
        previewPublisher.current = new MP.PreviewPublisher(targetEl);
        previewPublisher.current.on('audioLevelUpdated', (audioLevel) => {
          calculateAudioLevel(audioLevel);
        });
        previewPublisher.current.on('accessAllowed', (audioLevel) => {
          console.log('[createPreview] - accessAllowed');
          setAccessAllowed(DEVICE_ACCESS_STATUS.ACCEPTED);
          getDevices();
        });
        previewPublisher.current.on('accessDenied', (audioLevel) => {
          console.log('[createPreview] - accessDenied');
          setAccessAllowed(DEVICE_ACCESS_STATUS.REJECTED);
        });
        await previewPublisher.current.previewMedia({
          targetElement: targetEl,
          publisherProperties
        });

        setPreviewMediaCreated(true);
        console.log(
          '[Preview Created] - ',
          previewPublisher.current.getVideoDevice()
        );
      } catch (err) {
        console.log('[createPreview]', err);
      }
    },
    [calculateAudioLevel, getDevices]
  );

Estamos assinando alguns eventos do SDK para saber se o usuário permitiu o acesso ao dispositivo e para assinar eventos de nível de áudio, a fim de atualizar a interface do usuário e garantir ao usuário que o microfone está captando o áudio. Também exibiremos um alerta ao usuário caso o acesso aos dispositivos de áudio/Video tenha sido negado (consulte implementação).

Sala de espera

Um dos componentes mais importantes do nosso aplicativo é a componente WaitingRoom , pois é nele que usaremos o useDevices e usePreviewPublisher hooks. A sala de espera é uma página pré-chamada onde o usuário pode escolher o dispositivo de áudio e Video adequado, verificar se o microfone e a câmera estão funcionando e escolher um nome.

Esta é a aparência da nossa sala de espera:

Screenshot of waiting room on mobile deviceScreenshot of waiting room on mobile device

Temos algumas variáveis de estado que armazenarão as escolhas do usuário; isso é feito para permitir que o usuário entre na sala com o áudio ou o Video desativados, defina um nome ou altere os dispositivos de áudio:

const roomToJoin = location?.state?.room || '';
const [roomName, setRoomName] = useState(roomToJoin);
const [userName, setUserName] = useState('');
const [isRoomNameInvalid, setIsRoomNameInvalid] = useState(false);
const [isUserNameInvalid, setIsUserNameInvalid] = useState(false);
const [localAudio, setLocalAudio] = useState(
    user.defaultSettings.publishAudio
  );
const [localVideo, setLocalVideo] = useState(
    user.defaultSettings.publishVideo
);
const [localVideoSource, setLocalVideoSource] = useState(undefined); const [localAudioSource, setLocalAudioSource] = useState(undefined);
let [audioDevice, setAudioDevice] = useState('');
let [videoDevice, setVideoDevice] = useState('');

Criamos um UserContext que lida com as escolhas do usuário, como fontes de áudio e Video. Usaremos nosso usePreviewPublisher hook para criar e destruir uma pré-visualização do publisher em nossa sala de espera e teremos uma lista de dispositivos disponíveis, além de outras variáveis de estado úteis.

const {
    createPreview,
    destroyPreview,
    previewPublisher,
    logLevel,
    previewMediaCreated,
    deviceInfo,
    accessAllowed
  } = usePreviewPublisher();

A lógica começa assim que nosso componente for montado e tivermos o contêiner para nossa sala de espera. Vamos criar uma pré-visualização do publisher.

useEffect(() => {
    if (waitingRoomVideoContainer.current) {
      createPreview(waitingRoomVideoContainer.current);
    }

    return () => {
      destroyPreview();
    };
  }, [createPreview, destroyPreview]);

Temos um useEffect hook que é executado assim que a visualização é criada e inicializa a lista de dispositivos com o dispositivo atualmente em uso. Observe a chamada para getAudioDevice() e getVideoDevice() , já que o primeiro é uma promessa e o segundo é um método síncrono.

useEffect(() => {
    if (previewPublisher && previewMediaCreated && deviceInfo) {
      console.log('useEffect - preview', deviceInfo);
      previewPublisher.getAudioDevice().then(currentAudioDevice => {
        setAudioDevice(currentAudioDevice.deviceId);
      });
      const currentVideoDevice = previewPublisher.getVideoDevice();
      console.log('currentVideoDevice', currentVideoDevice);
      setVideoDevice(currentVideoDevice.deviceId);
    }
  }, [
    deviceInfo,
    previewPublisher,
    setAudioDevice,
    setVideoDevice,
    previewMediaCreated
  ]);

A lógica para trocar de dispositivo é praticamente a mesma para áudio e Video. Vamos explicá-la para o áudio, mas lembre-se de que você pode verificar a implementação da componente .

useEffect(() => {
    if (previewPublisher) {
      if (localVideo && !previewPublisher.isVideoEnabled()) {
        previewPublisher.enableVideo();
      } else if (!localVideo && previewPublisher.isVideoEnabled()) {
        previewPublisher.disableVideo();
      }
    }
  }, [localVideo, previewPublisher]);

Temos um ouvinte de eventos que é acionado quando o usuário altera o dispositivo de Video em uso:

const handleVideoSource = React.useCallback(
    e => {
      const videoDeviceId = e.target.value;
      setVideoDevice(e.target.value);
      previewPublisher.setVideoDevice(videoDeviceId);
      setLocalVideoSource(videoDeviceId);
    },
    [previewPublisher, setVideoDevice, setLocalVideoSource]
  );

Conclusão

Esta publicação mostra como integrar o novíssimo Video Express a um aplicativo React. O aplicativo implementa os principais recursos relacionados a um aplicativo de vídeo, como sala de espera, seleção de dispositivos, detecção do status da rede, compartilhamento de tela, chat e muito mais.

Fique à vontade para clonar o repositório do GitHub e comece a usá-lo em seu aplicativo.

Compartilhar:

https://a.storyblok.com/f/270183/400x266/5bd495df3c/enrico-portolan.png
Enrico PortolanAutor convidado

Enrico é um ex-funcionário da Vonage. Ele trabalhou como engenheiro de soluções, auxiliando a equipe de vendas com seus conhecimentos técnicos. Ele é apaixonado pela nuvem, por startups e por novas tecnologias. É cofundador de uma startup de WebRTC na Itália. No tempo livre, gosta de viajar e experimentar o máximo possível de comidas exóticas.