https://d226lax1qjow5r.cloudfront.net/blog/blogposts/create-a-multiparty-video-app-with-the-new-vonage-video-express/react-native_video-express_1200x600.png

Créez une application vidéo multipartite avec le nouveau Vonage Video Express

Publié le September 24, 2021

Temps de lecture : 6 minutes

Cet article a été rédigé en collaboration avec Javier Molina Sanz

Ce billet de blog vous aidera à développer une application Video multipartite basée sur ReactJS et le nouveau Video Express de Vonage. Video Express fournit par défaut les fonctionnalités suivantes :

  • Gestionnaire de salles et de participantsGestionnaire de salle : logique simplifiée de publication, d'abonnement et de gestion des flux de données

  • Gestion de la mise en pageLe gestionnaire de mise en page et l'interface utilisateur sont prêts à l'emploi, avec des composants personnalisables.

  • Optimisation de la qualité Video (taux de rafraîchissement et résolution) en fonction du nombre de participants, des tailles de rendu, de l'unité centrale et des conditions du réseau

  • Optimisation du réseau: Supprimez automatiquement la vidéo ou l'audio pour les participants qui ne sont pas visibles ou qui ne parlent pas, optimisant ainsi les ressources de la bande passante.

  • Facilité d'utilisation: Il offre une interaction plus naturelle en remplaçant la publication, l'abonnement et les flux par des salles et des participants.

Vous voulez sauter à la fin ? Vous pouvez trouver tout le code source de ce tutoriel sur GitHub.

Architecture des applications

L'application est divisée en deux sections principales, côté serveur et côté client : Côté serveur : un simple serveur NodeJS qui est en charge de la génération des identifiants et de la gestion des archives Côté client : une SPA (application à page unique) React qui utilise les Hooks React.

C'est du côté du client que les choses se passent vraiment. Grâce à Video Express, nous avons pu mettre en œuvre une application de vidéoconférence multipartite réactive et évolutive qui prend en charge les optimisations fastidieuses.

Client

L'application React s'appuie sur l'interface de l'application. @vonage/video-express via NPM. N'oubliez pas que vous pouvez également utiliser Video Express via une balise de script HTML. Documentation Video Express pour tous les détails.

L'application est basée sur React Hooks qui a été livré avec React 16.8. Ensuite, regardons de plus près les principaux hooks de cette application.

Salle d'utilisation

La UseRoom est celui qui gère le cycle de vie de notre Video Room. Grâce à Video Express, nous n'avons pas à gérer le cycle de vie de la session, de l'éditeur et de l'abonné. Au lieu de cela, nous n'avons qu'à instancier une salle puis d'utiliser la méthode room.join() qui, à son tour, s'occupera de tout dans les coulisses.

Tout d'abord, nous devons créer une fonction chargée d'initialiser notre objet Room et de joindre l'appel. Nous devons fournir notre authentification (apiKey, sessionId, et token) et d'autres paramètres optionnels utilisés en tant que paramètres de l'éditeur, tels que le conteneur où notre Room sera visible, et quelques paramètres optionnels de l'éditeur. userNameNous devons fournir notre authentification ( , , et ) et d'autres paramètres optionnels utilisés comme paramètres de l'éditeur, tels que la fonction

Comme nous allons utiliser le gestionnaire de mise en page par défaut fourni par Video Express, nous transmettons quelques paramètres de mise en page : définir une vue en grille pour la mise en page initiale définir un élément HTML personnalisé pour la vue de partage d'écran Vous pouvez trouver la liste complète des paramètres ici.

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));
    },
    [ ]
  );

Une fois l'objet Room a été initialisé, nous appelons la fonction startRoomListeners pour démarrer les récepteurs d'événements sur l'objet Room sur l'objet. Ensuite, nous appelons la méthode room.join() avec quelques options publisherSettings pour rejoindre la session. Nous avons besoin des auditeurs d'événements pour nous informer des événements tels que l'arrivée d'un nouveau participant, la création d'un nouveau flux de partage d'écran, la reconnexion d'un utilisateur à l'appel, 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);
      });
    }
  };

Notez que nous gardons également la trace des participants à la session afin de pouvoir afficher une liste des participants. Nous allons créer une variable d'état qui sera mise à jour lorsqu'un participant rejoindra ou quittera la salle.

Une autre fonction utile mise en place est le composant d'état du réseau composant d'état du réseau. Cette fonction sera utile lors de la déconnexion/reconnexion de l'utilisateur pour mettre à jour l'interface utilisateur, en informant l'utilisateur de ses problèmes de réseau.

Utiliser les appareils

De nos jours, il est très courant de disposer de plusieurs périphériques audio/vidéo. Certains utilisateurs préfèrent utiliser des écouteurs, tandis que d'autres préfèrent brancher une webcam externe sur leur ordinateur. Dans une application Video, il est essentiel de permettre à l'utilisateur de choisir entre différents dispositifs. L'option useDevices explique comment obtenir une liste des périphériques disponibles.

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

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

Nous avons mis en place un écouteur d'événements pour détecter un changement sur les appareils multimédias ; nous déclencherons notre fonction getDevices() lorsque cela se produira.

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);
    }
  }, []);

Notre fonction getDevices() appellera la fonction MP.geDevices() qui renvoie une liste d'appareils disponibles une fois que l'utilisateur a autorisé l'accès à l'appareil. Nous allons ensuite filtrer les appareils et remplir notre état avec les différents appareils disponibles.

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

UtiliserPreviewPublisher

Video Express vous aide également à mettre en œuvre l'expérience utilisateur avant l'appel. En effet, Video Express met en œuvre une fonction de PreviewPublisher . L'idée de cette classe PreviewPublisher est de permettre aux développeurs de prévisualiser facilement les médias et de s'assurer que vos périphériques (audio/vidéo) fonctionnent correctement sans avoir besoin de créer un objet Room objet.

Nous allons créer un aperçu pour permettre à l'utilisateur de choisir le bon appareil (s'il en a plusieurs), de s'assurer que le microphone capte l'audio et que la caméra fonctionne correctement. Consultez l'implémentation complète sur GitHub.

Nous allons d'abord récupérer les appareils disponibles à partir de notre crochet UseDevices.

const { deviceInfo, getDevices } = useDevices();

Après avoir initialisé l'éditeur de prévisualisation avec l'élément cible, nous appelons la méthode previewMedia pour visualiser le média. Nous mettrons également en place des récepteurs d'événements pour gérer l'accès à l'appareil et les événements audioLevel de l'appareil. Comme vous pouvez le voir, nous n'appellerons pas la fonction getDevices() tant que l'utilisateur n'a pas accordé l'autorisation aux appareils (lors de l'événement accessAllowed événement)

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

Nous nous abonnons à certains événements du SDK pour savoir si l'utilisateur a autorisé l'accès à l'appareil et pour nous abonner à des événements de niveau audio afin de mettre à jour l'interface utilisateur et de rassurer l'utilisateur sur le fait que le microphone capte de l'audio. Nous afficherons également une alerte à l'utilisateur si l'accès aux périphériques audio/vidéo a été refusé (voir mise en œuvre).

Salle d'attente

L'un des composants les plus importants de notre application est la fonction Salle d'attente car c'est là que nous utiliserons les fonctions useDevices et usePreviewPublisher et les crochets. La salle d'attente est une page de pré-appel où l'utilisateur peut choisir le bon appareil audio et vidéo, vérifier si le microphone et la caméra fonctionnent et choisir un nom.

Voici à quoi ressemble notre salle d'attente :

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

Nous avons quelques variables d'état qui contiennent le choix de l'utilisateur ; cela permet à l'utilisateur de rejoindre la pièce avec l'audio ou la vidéo désactivée, de définir un nom ou de changer les périphériques audio :

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('');

Nous avons créé un UserContext qui gère les choix de l'utilisateur tels que les sources audio et vidéo. Nous utiliserons notre usePreviewPublisher pour créer et détruire un aperçu de l'éditeur de notre salle d'attente et avoir une liste des appareils disponibles ainsi que d'autres variables d'état utiles.

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

La logique commence une fois que notre composant est monté et que nous avons le conteneur pour notre salle d'attente. Nous allons créer un aperçu de l'éditeur.

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

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

Nous avons un useEffect qui s'exécute une fois que l'aperçu a été créé et qui initialise la liste des appareils avec l'appareil actuellement utilisé. Notez l'appel à getAudioDevice() et getVideoDevice() car le premier est une promesse, et le second est une méthode synchrone.

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

La logique de changement de périphérique est presque la même pour l'audio et la Video. Nous l'expliquerons pour l'audio, mais rappelez-vous que vous pouvez vérifier l'implémentation de la fonction Salle d'attente du composant WaitingRoom.

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

Nous avons un écouteur d'événements qui se déclenche lorsque l'utilisateur change de périphérique vidéo :

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

Conclusion

Ce billet montre comment intégrer le tout nouveau Video Express à une application React. L'application met en œuvre les principales fonctionnalités liées à une application Video, telles que la salle d'attente, la sélection des appareils, la détection de l'état du réseau, le partage d'écran, le chat, etc.

N'hésitez pas à cloner le Github Repo et commencez à l'utiliser dans votre application.

Partager:

https://a.storyblok.com/f/270183/400x266/5bd495df3c/enrico-portolan.png
Enrico PortolanAuteur invité

Enrico est un ancien membre de l'équipe Vonage. Il a travaillé en tant qu'ingénieur de solutions, aidant l'équipe de vente avec son expertise technique. Il est passionné par le cloud, les startups et les nouvelles technologies. Il est le cofondateur d'une startup WebRTC en Italie. En dehors du travail, il aime voyager et goûter autant d'aliments bizarres que possible.