https://d226lax1qjow5r.cloudfront.net/blog/blogposts/how-to-use-mediastreams-with-vonage-video-api/add-video-elements.png

Cómo usar mediaStreams con Video API de Vonage

Publicado el April 24, 2024

Tiempo de lectura: 4 minutos

La Video API de Vonage facilita a los desarrolladores la incorporación de video a sus aplicaciones. Si no tienes necesidades muy específicas para tu aplicación de videoconferencia o necesitas un control de bajo nivel sobre los elementos de video, puedes usar la interfaz de usuario predeterminada que ofrece la Video API de Vonage, lo que reduce la complejidad para tu equipo de desarrollo. Sin embargo, si estás acostumbrado a trabajar con mediaStreams y manejar tus elementos de video o porque se ajusta mejor a los principios de diseño de cómo está construida tu aplicación de video, te tenemos cubierto.

Requisitos previos

  1. Una cuenta de Video API de Vonage. Haz clic en Regístrate para crear una si aún no la tienes.

  2. Esta publicación del blog asume que estás familiarizado con la Video API de Vonage, por lo que sólo nos centraremos en las necesidades específicas para publicar y suscribirte a tus elementos de video.

Elementos de Video personalizados

Para facilitar a los desarrolladores el uso de la Video API de Vonage, el Client SDK crea un elemento de video de manera predeterminada que puedes adjuntar al DOM. Sin embargo, puedes proporcionar tu propio elemento de video HTML si deseas un mayor control. Para esta entrada de blog, nos referiremos a esta aplicación React como ejemplo de cómo acceder al mediaStream y utilizarlo como srcObject para tu elemento de vídeo personalizado. Siéntete libre de clonarla y probarla.

Editorial

En primer lugar, en las propiedades de nuestro editor, tenemos que desactivar la interfaz de usuario por defecto, como mencionamos en la documentación para desarrolladores. La función de publicación se define en el gancho del 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);
    }
  }

La lógica para publicar en la sesión se define en la función publishAttempt. Pero por simplicidad, vamos a codificar un único reintento. Tenga en cuenta que no estamos pasando ningún elemento de destino a la función de publicación, ya que queremos acceder al mediaStream subyacente y utilizarlo con nuestro elemento de vídeo.

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;
    }
  }

Ahora, tenemos que escuchar el videoElementCreated evento que se envía al editor en este caso. Ahora, en lugar de utilizar el elemento de vídeo enviado en el evento videoElementCreated directamente, vamos a acceder a su mediaStream para alimentar nuestro propio elemento de vídeo. Comprueba la implementación del componente 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;

Añadimos el atributo autoplay que hará que los nuevos flujos asignados al elemento se reproduzcan automáticamente. El atributo playsinline permite que el vídeo se reproduzca en línea en lugar de sólo en pantalla completa. También estamos añadiendo el atributo muted para evitar el eco porque la API de Video de Vonage reproducirá audio a través del elemento de video creado pero no renderizado en el DOM.

Todo lo que estamos haciendo con nuestro elemento de vídeo es rellenar el srcObject con el mediaStream proporcionado por el evento videoElementCreated evento. El siguiente código muestra cómo se obtiene el mediaStream del oyente del evento.

//hooks/publisher.jsx

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

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

A continuación, en nuestra Página de la habitaciónpodemos renderizar nuestro componente CustomPublisher con el mediaStream como prop. Tenga en cuenta que mPublisher es sólo la importación del hook publisher() y pubStream es la pieza de estado que contiene nuestro mediaStream

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

Abonado

El enfoque que vamos a adoptar para renderizar los suscriptores es similar, pero con una salvedad. En el momento de escribir esta entrada de blog para los clientes que utilizan versiones de JS > 2.24.7, si está trabajando con mediaStreams en el lado del suscriptor, es necesario seguir los pasos descritos en este artículo de soporte.

En primer lugar, tenemos que desactivar la interfaz de usuario por defecto como lo hicimos para el editor. Luego, en este caso, vamos a establecer 1 pieza de estado. Vamos a almacenar el elemento Video creado por Vonage para que podamos acceder al mediaStream subyacente. Verás lo que hacemos con el elemento Video en un momento. Esta lógica está definida en el Contexto de sesión

 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 });
    }
  }

En el CustomSubscriber componenteproporcionaremos nuestro elemento de vídeo y adjuntaremos un receptor de eventos al elemento de vídeo creado por Vonage para que podamos actualizar nuestro mediaStream cuando cambie, como se explica en el artículo.

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;

Como en el caso del Publisher, obtenemos el mediaStream del elemento Video y lo adjuntamos a nuestro elemento Video. La diferencia es que ahora tenemos que añadir un oyente de eventos para entender cuando cambia el mediaStream y actualizar nuestro elemento de vídeo con el nuevo mediaStream si cambia.

En este punto, podemos renderizar los suscriptores en nuestra Página de Sala.

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

Tenga en cuenta que también puede pasar el objeto suscriptor al componente CustomSubscriber para actualizar la interfaz de usuario en función del estado. Por ejemplo, puede mostrar un icono superpuesto de activación/desactivación del micrófono en función de la propiedad subscriber.stream.hasAudio del suscriptor. Esto te permitirá no tener que manipular el DOM insertando/eliminando elementos HTML sobre el elemento de video creado por la Video API de Vonage. En su lugar, renderizarás diferentes estados según las diferentes propiedades del suscriptor basadas en la lógica de tu aplicación.

Importante

Tenga en cuenta que si sigue este enfoque y decide utilizar sus elementos de vídeo en lugar de los elementos de vídeo creados por el SDK, no podrá utilizar algunas funciones vinculadas al elemento de vídeo del SDK. No se beneficiará de iniciales del editor y backgroundImageUri ya que la lógica de estas funciones se basa en el elemento de video creado por Vonage.

Conclusión

En conclusión, Vonage permite una gran flexibilidad a la hora de desarrollar aplicaciones de videoconferencia. De manera predeterminada, Vonage crea un elemento de video que puedes adjuntar al DOM. Sin embargo, también admitimos casos de uso en los que necesitas proporcionar tu elemento de video accediendo a los mediaStreams de los elementos de video que la Video API de Vonage crea para ti.

Para estar al día de las últimas novedades, conéctate con nosotros en nuestra Desarrolladoresen X, antes conocido como Twittery en eventos.

Compartir:

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.