https://a.storyblok.com/f/270183/47427/b358257f00/add-video-elements.png

Como usar o mediaStreams com a Video API da Vonage

Publicado em April 24, 2024

Tempo de leitura: 4 minutos

A Video API do Vonage facilita aos desenvolvedores a incorporação de vídeo em suas applications. Se você não tiver necessidades muito específicas para seu aplicativo de videoconferência ou não precisar de controle de baixo nível sobre os elementos de vídeo, pode usar a interface de usuário padrão fornecida pela Video API do Vonage, reduzindo a complexidade para sua equipe de desenvolvimento. No entanto, se você estiver acostumado a trabalhar com mediaStreams e gerenciar seus próprios elementos de vídeo — ou simplesmente porque isso se encaixa melhor nos princípios de design da construção do seu aplicativo de vídeo —, nós temos a solução ideal para você.

Pré-requisitos

  1. Uma conta da Video API da Vonage. Clique em “Inscrever-se” para criar uma, caso ainda não tenha uma.

  2. Esta postagem no blog pressupõe que você já esteja familiarizado com a Video API da Vonage; portanto, vamos nos concentrar apenas nas necessidades específicas para publicar e assinar seus elementos de vídeo.

Elementos de video personalizados

Para facilitar o uso da Video API da Vonage pelos desenvolvedores, o Client SDK cria, por padrão, um elemento de vídeo que você pode anexar ao DOM. No entanto, você pode fornecer seu próprio elemento de vídeo em HTML caso deseje ter mais controle. Para esta postagem do blog, vamos nos referir a este aplicativo React como exemplo de como acessar o mediaStream e usá-lo como srcObject para seu elemento de vídeo personalizado. Fique à vontade para cloná-la e experimentá-la.

Editora

Em primeiro lugar, nas propriedades do nosso editor, precisamos desativar a interface de usuário padrão, conforme mencionamos na documentação para desenvolvedores. A função de publicação é definida no gancho do editor

//hooks/publisher.jsx 
async function publish(name, extraData) {
    try {
      if (!mSession.session) throw new Error('You are not connected to session');
      const options = {
        insertMode: 'append',
        name: name,
        resolution: '1280x720',
        publishAudio: user.defaultSettings.publishAudio,
        publishVideo: user.defaultSettings.publishVideo,
        audioSource: user.defaultSettings.audioSource,
        videoSource: user.defaultSettings.videoSource,
        insertDefaultUI: false,
        audioFallback: {
          publisher: true,
        },
      };
      const finalOptions = Object.assign({}, options, extraData);
      setPublisherOptions(finalOptions);
      console.log(finalOptions);
      const newPublisher = OT.initPublisher(null, finalOptions);

      publishAttempt(newPublisher, 1);
      publisher.current = newPublisher;
   
    } catch (err) {
      console.log(err.stack);
    }
  }

A lógica para publicar na sessão é definida na função `publishAttempt`. Mas, para simplificar, vamos codificar manualmente uma única tentativa de repetição. Observe que não estamos passando nenhum elemento de destino para a função `publish`, já que queremos acessar o `mediaStream` subjacente e usá-lo com nosso elemento de Video.

async function publishAttempt(publisher, attempt = 1, noRetry = true) {
    console.log(`Attempting to publish in ${attempt} try`);

    publisher.on('destroyed', handleDestroyed);
    publisher.on('streamDestroyed', handleStreamDestroyed);
  publisher.on('videoElementCreated', handleVideoElementCreated);

    const { retry, error } = await new Promise((resolve, reject) => {
      mSession.session.publish(publisher, (err) => {
        if (err && noRetry) {
          resolve({ retry: undefined, error: err });
        }
        if (err && attempt < 3) {
          resolve({ retry: true, error: err });
        }
        if (err && attempt >= 3) {
          resolve({ retry: false, error: err });
        } else {
          resolve({ retry: false, error: undefined });
        }
      });
    });

    if (retry) {
      // Wait for 2 seconds before attempting to publish again
      await delay(2000 * attempt);
      await publishAttempt(publisher.current, attempt + 1);
    } else if (error) {
      if (noRetry) return;
      alert("Publish error");
      mSession.disconnect();
      setIsPublishing(false);
      publisher.current = null;
    } else {
      setIsPublishing(true);
      publisher.current = publisher;
    }
  }

Agora, precisamos ouvir o videoElementCreated evento que é disparado no publisher, neste caso. Agora, em vez de usar diretamente o elemento Video disparado no videoElementCreated evento diretamente, vamos acessar seu mediaStream para alimentá-lo em nosso próprio elemento Video. Confira a implementação do componente CustomPublisher.

//Components/CustomPublisher
import React, { useEffect, useMemo, useState, useRef } from 'react';
function CustomPublisher({ mediaStream }) {
  const videoRef = useRef(null);
  useEffect(() => {
    if (mediaStream) {
      videoRef.current.srcObject = mediaStream;
    }
  }, [mediaStream]);
  return <video width="100%" ref={videoRef} autoPlay playsInline muted></video>;
}

export default CustomPublisher;

Estamos adicionando o autoplay atributo, o que fará com que novos streams atribuídos ao elemento sejam reproduzidos automaticamente. O playsinline permite que o vídeo seja reproduzido em linha, em vez de apenas em tela cheia. Também estamos adicionando o atributo “muted” para evitar eco, pois a Video API da Vonage reproduzirá o áudio por meio do elemento de vídeo criado, mas não renderizado no DOM.

Tudo o que estamos fazendo com nosso elemento de Video é preencher o srcObject com o mediaStream fornecido pelo videoElementCreated evento. O código a seguir mostra como obter o mediaStream a partir do ouvinte de eventos.

//hooks/publisher.jsx

publisher.on('videoElementCreated', handleVideoElementCreated);
//hooks/publisher 

function handleVideoElementCreated({ element }) {
    const stream = element.srcObject;
    setPubStream(stream);
  }

Em seguida, na nossa página “Quartos”, basta renderizar nosso componente CustomPublisher com o `mediaStream` como uma propriedade. Observe que mPublisher é apenas a importação do publisher() hook, e pubStream é o estado que contém nosso mediaStream

//Pages/Room.index.js
  {mPublisher.pubStream && <CustomPublisher mediaStream={mPublisher.pubStream}></CustomPublisher>}

Assinante

A abordagem que vamos adotar para renderizar os assinantes é semelhante, mas com uma ressalva. No momento da redação desta postagem no blog, para clientes que utilizam versões do JS superiores a 2.24.7, caso estejam trabalhando com mediaStreams no lado do assinante, é necessário seguir as etapas descritas neste artigo de suporte.

Primeiramente, precisamos desativar a interface de usuário padrão, assim como fizemos para o Publisher. Em seguida, neste caso, vamos definir um estado. Vamos armazenar o elemento de vídeo criado pela Vonage para que possamos acessar o mediaStream subjacente. Você verá o que faremos com o elemento de vídeo daqui a pouco. Essa lógica está definida no contexto da sessão

 async function subscribe(stream, session, options = {}) {
    console.log('request to subscribe');
    if (session) {
      console.log(session);
      const finalOptions = Object.assign({}, options, {
        insertMode: 'append',
        width: '100%',
        height: '100%',
        insertDefaultUI: false,
      });
      const subscriber = session.current.subscribe(stream, null, finalOptions);
     subscriber.on('videoElementCreated', function (event) {
        const element = event.element;
        element.setAttribute('id', event.target.streamId);
        setSubscriberElements((prevStreams) => [...prevStreams, { element, subscriber     }]);
      });
      addSubscribers({ subscriber });
    }
  }

No CustomSubscriber componente, vamos fornecer nosso elemento de Video e anexar um ouvinte de eventos ao elemento de Video criado pela Vonage para que possamos atualizar nosso mediaStream quando ele mudar, conforme explicado no artigo.

import React, { useEffect, useRef } from 'react';

function CustomSubscriber({ element }) {
  const videoRef = useRef(null);
  const mediaStream = element.srcObject;
  useEffect(() => {
    if (mediaStream && videoRef.current) {
      videoRef.current.srcObject = mediaStream;
      videoRef.current.setAttribute('id', element.id);
      const handleStreamChange = () => {
        if (mediaStream !== element.srcObject) {
          videoRef.current.srcObject = element.srcObject;
        }
      };

      element.addEventListener('play', handleStreamChange);

      return () => {
        element.removeEventListener('play', handleStreamChange);
      };
    }
  }, [element, mediaStream]);

  return <video width="100%" ref={videoRef} autoPlay playsInline muted></video>;
}

export default CustomSubscriber;

Assim como no caso do Publisher, estamos obtendo o mediaStream do elemento Video e atribuindo-o ao nosso elemento Video. A diferença é que agora precisamos adicionar um ouvinte de eventos para detectar quando o mediaStream for alterado e atualizar nosso elemento Video com o novo mediaStream, caso haja alteração.

Nesse ponto, podemos exibir os assinantes na nossa Página da Sala.

{mSession.subscriberElements.length > 0 &&
            mSession.subscriberElements.map((element, index) => <CustomSubscriber key={index} element={element}></CustomSubscriber>)}

Observe que você também pode passar o objeto assinante para o CustomSubscriber componente para atualizar sua interface de usuário com base no estado. Por exemplo, você pode exibir um ícone de sobreposição de microfone ligado/desligado dependendo da subscriber.stream.hasAudio propriedade. Isso permitirá que você não precise manipular o DOM inserindo ou removendo elementos HTML sobre o elemento de vídeo criado pela Video API da Vonage. Em vez disso, você renderizará diferentes estados com base nas diferentes propriedades do assinante, de acordo com a lógica do seu aplicativo.

É importante observar que

Observe que, se você seguir essa abordagem e decidir usar seus próprios elementos de vídeo em vez dos criados pelo SDK, não poderá utilizar alguns recursos vinculados ao elemento de vídeo do SDK. Você não terá acesso a iniciais do editor e do backgroundImageUri , pois a lógica desses recursos é baseada no elemento de Video criado pela Vonage.

Conclusão

Em conclusão, o Vonage oferece grande flexibilidade no desenvolvimento de aplicativos de videoconferência. Por padrão, o Vonage cria um elemento de vídeo que você pode anexar ao DOM. No entanto, também oferecemos suporte a casos de uso em que é necessário fornecer seu próprio elemento de vídeo, acessando os mediaStreams dos elementos de vídeo que a Video API do Vonage cria para você.

Para ficar por dentro das últimas notícias, siga-nos no nosso canal do Slack para desenvolvedores no Slack da Comunidade de Desenvolvedores, no X, anteriormente conhecido como Twitter, e em eventos.

Compartilhar:

https://a.storyblok.com/f/270183/384x384/6007824739/javier-molina-sanz.png
Javier Molina Sanz

Javier studied Industrial Engineering back in Madrid where he's from. He is now one of our Solution Engineers, so if you get into trouble using our APIs he may be the one that gives you a hand. Out of work he loves playing football and travelling as much as he can.