Escrevi algumas postagens recentemente sobre transmissões ao vivo multi-host com o Amazon Interactive Video Service (Amazon IVS). É um recurso empolgante que abre mundos de possibilidades que simplesmente não estavam disponíveis até recentemente. Primeiro, vimos como criar um aplicativo de bate-papo ao vivo com vários hosts .
Em seguida, vimos como transmitir essa sessão de bate-papo ao vivo para um canal do Amazon IVS.
Quando analisamos a adição de participantes de bate-papo ao cliente de transmissão naquele último post, você provavelmente notou que trapaceei um pouco e codifiquei os valores de VideoComposition
que informam ao cliente de transmissão o tamanho e a posição do vídeo do participante no cliente.
Bem - enganado é uma palavra forte - digamos que simplifiquei intencionalmente o código para focar no processo de transmissão de uma sessão de bate-papo ao vivo.
Basicamente, o que procuramos aqui é modificar o tamanho e a posição do vídeo do participante na transmissão para que, quando houver um vídeo, o layout fique mais ou menos assim:
Mas quando houver dois vídeos, o layout mudará para algo assim:
E quando são cinco:
Você entendeu - um layout dinâmico que muda com base na quantidade de participantes.
Nesta postagem, veremos uma abordagem que você pode utilizar para tornar a criação de um layout dinâmico um pouco mais fácil. Desenvolveremos a solução na última postagem, portanto, se você ainda não leu essa postagem, provavelmente é uma boa ideia fazê-lo agora.
Na última postagem, ouvimos um evento chamado STAGE_PARTICIPANT_STREAMS_ADDED
. No manipulador de eventos desse evento, adicionamos nossos participantes ao DOM e renderizamos o áudio e o vídeo para a instância IVSBroadcastClient
.
Para renderizar um layout dinâmico, precisaremos rastrear quantos participantes estão atualmente na sessão, então adicionaremos um array participantIds
como uma variável global. Vamos modificar o manipulador de eventos para enviar o ID do participante atual para esse array.
stage.on(StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED, (participant, streams) => { //add participant id to array participantIds.push(participant.id); renderParticipant(participant, streams); renderVideosToClient(participant, streams.find(s => s.streamType === StreamType.VIDEO)); renderAudioToClient(participant, streams.find(s => s.streamType === StreamType.AUDIO)); updateVideoCompositions(); });
No último post, mencionei que o método updateVideoCompositions()
não foi mostrado porque a implementação poderia variar. Falaremos sobre uma implementação possível daqui a pouco.
Por enquanto, vamos dar uma olhada em como podemos obter uma configuração de layout dinâmico em vez de codificá-la como fizemos no último post.
Uma maneira de obter um tamanho e uma posição dinâmicos é percorrer o array de participantes e calculá-los com base no número de participantes, no tamanho do <canvas>
e na quantidade desejada de linhas, colunas e preenchimento. Mas, por quê ?
Isso soa como um monte de código difícil e trabalho desnecessário quando você percebe que esses valores nunca mudam. Se você tiver um participante, o vídeo terá um tamanho fixo e centralizado na <canvas>
.
Não importa quantos participantes são adicionados - o layout de cada vídeo sempre será o mesmo para um determinado número de participantes. Então, por que desperdiçar tempo e ciclos de CPU quando poderíamos pré-calcular esses valores e armazená-los em uma matriz de matrizes?
Para minha demonstração, passei algum tempo determinando os melhores valores com 30 minutos intensivos com caneta, papel e calculadora para determinar os valores de composição para cada layout possível. Observação: eu não era formado em matemática ou arte, conforme evidenciado pelo esboço a seguir.
Para esta demonstração, limitei minha transmissão ao vivo para mostrar apenas vídeos para os primeiros 6 participantes. Seu caso de uso pode ditar algo diferente, mas ter mais de 6 vídeos de participantes em uma transmissão ao vivo torna-se um pouco complicado na minha experiência.
Aqui está o resultado dos meus cálculos:
const layouts = [ [{ height: 720, width: 1280, x: 320, y: 180 }], [{ height: 450, width: 800, x: 80, y: 315 }, { height: 450, width: 800, x: 1040, y: 315 }], [{ height: 450, width: 800, x: 80, y: 45 }, { height: 450, width: 800, x: 1040, y: 45 }, { height: 450, width: 800, x: 560, y: 585 }], [{ height: 450, width: 800, x: 80, y: 45 }, { height: 450, width: 800, x: 1040, y: 45 }, { height: 450, width: 800, x: 80, y: 585 }, { height: 450, width: 800, x: 1040, y: 585 }], [{ height: 337, width: 600, x: 20, y: 100 }, { height: 337, width: 600, x: 650, y: 100 }, { height: 337, width: 600, x: 1280, y: 100 }, { height: 337, width: 600, x: 340, y: 640 }, { height: 337, width: 600, x: 980, y: 640 }], [{ height: 337, width: 600, x: 20, y: 100 }, { height: 337, width: 600, x: 650, y: 100 }, { height: 337, width: 600, x: 1280, y: 100 }, { height: 337, width: 600, x: 20, y: 640 }, { height: 337, width: 600, x: 650, y: 640 }, { height: 337, width: 600, x: 1280, y: 640 }] ];
Isso pode parecer complicado, mas considere que cada elemento no elemento externo da matriz contém uma matriz de composições para cada vídeo.
Se houver 3 participantes, podemos referenciar o terceiro elemento no array externo, e a posição do ID do participante no participantIds
determinará qual composição será aplicada a esse vídeo.
Podemos modificar nossa função renderVideosToClient()
para obter a composição adequada e usar esses valores quando adicionamos o vídeo ao cliente de transmissão.
const renderVideosToClient = async (participant, stream) => { const participantId = participant.id; const videoId = `video-${participantId}`; // get the index of this participantId const pIdx = participantIds.indexOf(participantId); let composition = layouts[participantIds.length - 1][pIdx]; config.index = 2; const mediaStream = new MediaStream(); mediaStream.addTrack(stream.mediaStreamTrack); broadcastClient.addVideoInputDevice(mediaStream, videoId, composition); };
Mas lembre-se - se fizermos isso apenas quando um participante for adicionado, as composições de vídeo anteriores ainda refletirão a composição que foi aplicada quando foram adicionadas. É aí que a função updateVideoCompositions()
entra em cena.
Aqui, fazemos um loop sobre o participantIds
, pegamos a composição adequada de layouts
e usamos o updateVideoDeviceComposition()
(broadcastClient
.
const updateVideoCompositions = async () => { let idx = 0; for (const p of participantIds) { const videoId = `video-${p}`; let config = layouts[filteredParticipantIds.length - 1][idx]; config.index = 2; broadcastClient.updateVideoDeviceComposition(videoId, config); idx = idx + 1; } };
Também devemos garantir que, quando um participante sair do palco, removamos o ID do participante da matriz e atualizemos novamente a composição de todos os vídeos.
stage.on(StageEvents.STAGE_PARTICIPANT_STREAMS_REMOVED, (participant, streams) => { const participantId = participant.id; // remove participant id from array const pIdx = participantIds.findIndex(id => id === participantId); participantIds.splice(pIdx, 1); const videoTrackId = `video-${participantId}`; const audioTrackId = `audio-${participantId}`; if (broadcastClient.getVideoInputDevice(videoTrackId)) broadcastClient.removeVideoInputDevice(videoTrackId); if (broadcastClient.getAudioInputDevice(audioTrackId)) broadcastClient.removeAudioInputDevice(audioTrackId); const videoId = `${participantId}-video`; document.getElementById(videoId).closest('.participant-col').remove(); updateVideoCompositions(); });
Conforme mencionado acima, você provavelmente desejará limitar a quantidade de vídeos adicionados à transmissão ao vivo por meio do cliente de transmissão. Você pode querer adicionar uma imagem estática em vez do vídeo final para mostrar que há mais participantes do que o mostrado:
Nesta postagem, aprendemos uma abordagem para layouts dinâmicos ao transmitir um estágio de vários hosts com o Amazon IVS. Em uma postagem futura, veremos opções adicionais para transmissão com vários hosts. Como sempre, se você tiver dúvidas ou comentários, deixe-os abaixo.
Também publicado aqui