
Compartilhar:
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.
Crie um aplicativo de vídeo para várias pessoas com o novo Vonage Video Express
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 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:
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.