Application vidéo avec salons de groupe, Twilio Programmable Video, React, TypeScript et Express - Partie 2

May 17, 2021
Rédigé par
Mia Adjei
Twilion
Révisé par

Construire une application vidéo avec des salons de groupe avec Twilio Programmable Video, React, TypeScript et Express — Partie 2

Dans le tutoriel précédent, vous avez créé la partie serveur d'une application vidéo permettant également de créer des salles de groupe. Il est désormais temps de développer le côté client de votre application à l'aide de React et de TypeScript.

À la fin de ce tutoriel, vous saurez créer et utiliser des salles de groupe dans votre application vidéo.

Allez, c'est parti !

Conditions préalables

Vous aurez besoin des éléments ci-dessous :

  • Un compte Twilio gratuit. (Si vous vous inscrivez ici, vous recevrez un crédit Twilio de 10 $ lorsque vous passerez à un compte payant !)
  • Node.js (version 14.16.1 ou supérieure) et npm installé sur votre machine.
  • HTTPie ou cURL.
  • Le code du tutoriel précédent. (Voir l'étape suivante pour obtenir plus de détails.)

Obtenir le code de démarrage

Si vous venez de terminer le tutoriel précédent, vous disposez déjà du code côté serveur mis à jour dont vous avez besoin pour passer à la section suivante.

Cependant, si vous ne disposez pas de ce code, vous le trouverez dans la branche updated-server du répertoire GirHub react-video-breakouts. Pour obtenir le code, choisissez un emplacement sur votre machine où configurer le projet. Ensuite, ouvrez une fenêtre de terminal et exécutez la commande suivante pour cloner la branche updated-server  du répertoire :

git clone -b updated-server  https://github.com/adjeim/react-video-breakouts.git

Accédez ensuite au répertoire racine du projet et installez les dépendances nécessaires en exécutant les commandes suivantes :

cd react-video-breakouts
npm install

Configurez un fichier .env pour vos variables d'environnement en exécutant la commande suivante dans votre terminal :

cp .env.template .env

Ensuite, ouvrez le fichier .env dans votre éditeur de code et remplacez les valeurs d'espace réservé pour TWILIO_ACCOUNT_SID, TWILIO_API_KEY et TWILIO_API_SECRET par vos propres informations d'identification, qui se trouvent dans la console Twilio. Pour obtenir des instructions plus détaillées sur la recherche de ces informations d'identification, dans , consultez la section Installer les dépendances et définir les variables d'environnement le tutoriel précédent.

Une fois que vous avez configuré les variables d'environnement, vous pouvez passer à l'étape suivante.

Exécuter le serveur Express et l'application React

Pour commencer à construire votre application côté client, commencez par vous assurer que votre serveur Express est en cours d'exécution. Si vous venez directement du tutoriel précédent, il se peut que ce serveur s'exécute déjà dans une fenêtre de terminal. Si ce n'est pas le cas, démarrez le serveur Express en ouvrant une fenêtre de terminal et en exécutant la commande suivante :

npm run server

Une fois le serveur démarré, une instruction de log s'affiche dans la fenêtre du terminal pour vous indiquer que le serveur est en cours d'exécution :

Express server running on port 5000

Si vous examinez le répertoire src dans react-video-breakouts, vous verrez qu'une application React de base est déjà configurée pour vous. Ouvrez une deuxième fenêtre de terminal et exécutez la commande suivante pour démarrer l'application React :

npm run start

Une fois l'application démarrée, une fenêtre de navigateur s'ouvre, pointant vers http://localhost:3000/. L'application initiale se présente comme suit :

Fenêtre du navigateur pointant sur localhost:3000/. Texte noir « My Video Chat » (Mon chat vidéo) et « Video chat coming soon » (Le chat vidéo arrive bientôt) sur fond blanc.

Il y a clairement de nombreux autres éléments à ajouter ici avant de pouvoir démarrer un chat vidéo.

Mettre à jour le composant App et server.ts

Commencez par ouvrir src/App.tsx dans votre éditeur de code. Dans ce tutoriel, vous utiliserez des hooks React. Mettez donc à jour l'importation de sorte à inclure les hooks useEffect et useState. De plus, puisque vous utiliserez Twilio Video, importez aussi la fonction connect et la classe Room.

Les importations mises à jour doivent ressembler au code ci-dessous :

import React, { useEffect, useState } from 'react';
import { connect, Room as VideoRoom } from 'twilio-video';
import './App.css';

Ensuite, créez des interfaces pour MainRoom et BreakoutRoom afin de stocker les détails de la salle vidéo que vous allez extraire du côté serveur. Ajoutez le code suivant juste en dessous de vos instructions d'importation :

export interface MainRoom {
  _id: string;
  name: string;
  breakouts: BreakoutRoom[]
}

export interface BreakoutRoom {
  _id: string;
  name: string;
}

Ensuite, mettez à jour l'état avec quelques variables que vous utiliserez dans ce composant :

const App = () => {
  // State variables
  const [identity, setIdentity] = useState('');
  const [room, setRoom] = useState<VideoRoom>();
  const [roomList, setRoomList] = useState<MainRoom[]>([]);
  const [showControls, setShowControls] = useState(false);
  const [newRoomName, setNewRoomName] = useState('');
  const [parentSid, setParentSid] = useState('');

Voici à quoi correspondent ces variables :

  • identity : identité de l'utilisateur
  • room : salle vidéo
  • roomList : liste des salles de votre serveur
  • showControls : indique s'il faut afficher ou non les commandes de création de salles dans l'interface utilisateur
  • newRoomName : nom de la nouvelle salle que vous souhaitez créer
  • parentSid : sid de la salle principale à laquelle une salle de groupe sera associée

Ajouter une communication en temps réel avec Socket.IO

Maintenant que vous avez les débuts de la configuration côté client, il est temps d'ajouter Socket.IO, à savoir une librairie qui permet la communication en temps réel basée sur les événements entre le navigateur et le serveur. Dans les sections suivantes, nous utiliserons cette librairie côté serveur pour émettre des événements lorsque de nouvelles salles seront créées, et côté client pour recevoir ces événements, puis prendre des mesures afin d'obtenir la liste des salles mise à jour.

Ajoutez Socket.IO à votre projet en exécutant la commande suivante dans votre terminal :

npm install socket.io socket.io-client

Ensuite, ouvrez votre fichier .env et ajoutez une variable côté client pour REACT_APP_SOCKET_URL juste en dessous des autres variables d'environnement :

TWILIO_ACCOUNT_SID=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
TWILIO_API_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
TWILIO_API_SECRET=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
REACT_APP_SOCKET_URL=http://localhost:5000/

Sachant que cette application React a été créée à l'aide de create-react-app, seules les variables d'environnement portant le préfixe REACT_APP_ seront disponibles côté client. Le côté client ne pourra pas accéder à vos informations d'identification Twilio.


Pour en savoir plus, consultez la documentation relative à la création de l'application React ici.

Cette nouvelle variable d'environnement permet à votre application React de recevoir les événements émis à partir de localhost:5000, qui est l'endroit où votre serveur est exécuté.

Étant donné que vous avez modifié une variable d'environnement, vous devez arrêter votre application React et la redémarrer à nouveau pour que la nouvelle variable d'environnement soit accessible. Dans la fenêtre de terminal où l'application React est en cours d'exécution, appuyez sur Ctrl + C sur votre clavier, puis exécutez npm run start à nouveau.

Ajouter Socket.IO à votre serveur Express

Ouvrez ensuite server.ts dans votre éditeur de code et ajoutez l'instruction d'importation suivante à votre liste d'importations :

import twilio, { Twilio } from 'twilio';
import PouchDB from 'pouchdb';
import { Server } from 'socket.io';

Faites défiler jusqu'au bas de server.ts et mettez à jour votre bloc app.listen. Transmettez votre serveur Express en tant qu'argument pour créer le serveur Socket.IO, comme indiqué dans le code suivant :

const server = app.listen(port, () => {
  console.log(`Express server running on port ${port}`);
});

const io = new Server(server);

Ensuite, mettez à jour la fonction de sorte que createRoom émette un événement Main room created lorsqu'une nouvelle salle principale est créée. Modifiez les lignes de code suivantes comme décrit ci-dessous :

// Change this code:
      return response.status(200).send({
        message: `New video room ${room.uniqueName} created`,
        room: mainRoom
      });


// To this:
      response.status(200).send({
        message: `New video room ${room.uniqueName} created`,
        room: mainRoom
      });

      io.emit('Main room created');
      return;

Vous devrez également émettre un événement lors de la création d'une salle de groupe. Modifiez également le code suivant dans createBreakoutRoom, comme décrit ci-dessous :

// Change this code:
      return response.status(200).send({
        message: `Breakout room ${breakoutRoom.uniqueName} created`,
        room: mainRoom
      });


// To this:
      response.status(200).send({
        message: `Breakout room ${breakoutRoom.uniqueName} created`,
        room: mainRoom
      });

      io.emit('Breakout room created');
      return;

Ajouter Socket.IO à votre application React

Il est maintenant temps de configurer Socket.IO côté client. Ajoutez la ligne suivante à votre liste d'importations dans src.App.tsx :

import React, { useEffect, useState } from 'react';
import { connect, Room as VideoRoom } from 'twilio-video';
import './App.css';
import io, { Socket } from 'socket.io-client';

Ajoutez ensuite une autre variable d'état pour Socket.IO à la liste des autres variables d'état dans src/App.tsx :

  const [newRoomName, setNewRoomName] = useState('');
  const [parentSid, setParentSid] = useState('');
  const [socket, setSocket] = useState<Socket | null>(null);

Ajoutez un bloc useEffect à votre composant App juste en dessous de la liste des variables d'état. Cela permet de configurer l'instance socket lorsque le composant est monté et de définir le socket dans l'état du composant :

  useEffect(() => {
    if (process.env.REACT_APP_SOCKET_URL) {
      const socket = io(process.env.REACT_APP_SOCKET_URL, { transports: ['websocket'] });

      setSocket(socket);
    }
  }, []);

Maintenant que Socket.IO est configuré, il est temps de créer des salles vidéo.

Créer et afficher une nouvelle salle vidéo

Pour créer une salle vidéo, vous devez saisir un nom pour la salle et envoyer cette information afin que le serveur puisse créer la salle.

Dans App.tsx, remplacez le code figurant dans l'instruction return par le code suivant :

  return (
    <div className="app">
      <label className="start">
        <input
          type="checkbox"
          checked={showControls} />
        Show Room Controls
      </label>

      { showControls &&
        <div className="controls">
          <label className="start">
            Name your room:
            <input
              value={newRoomName}
              onChange={(event) => {
                setNewRoomName(event.target.value)}} />
          </label>
          <button disabled={newRoomName === '' ? true : false}>
            {room ? 'Create Breakout Room' : 'Create Room'}
          </button>
        </div>
      }
    </div>
  );

Avec ce code, vous avez créé un emplacement où les utilisateurs peuvent saisir le nom de la salle vidéo qu'ils souhaitent créer. Le nom de cette nouvelle salle sera enregistré dans l'état du composant. Dans le code ci-dessus, vous voyez aussi que si le nom d'une salle n'est pas présent dans le champ de saisie, le bouton Create Room (Créer une salle) est désactivé.

Vous vous demandez peut-être à quoi correspond cette ligne :{room ? 'Create Breakout Room' : 'Create Room'}. Ici, si l'utilisateur se trouve déjà dans une salle vidéo, le texte se transforme en Create Breakout Room (Créer une salle de groupe) afin que l'utilisateur puisse créer des salles de groupe associées à la salle principale qu'il a déjà rejointe. Pas mal, n'est-ce pas ?

Le fait que ce champ de saisie soit visible ou non dépend de la variable showControls. Si vous examinez votre application dans la fenêtre de navigateur, vous verrez que la case à côté de Show Room Controls (Afficher les commandes de salle) n'est pas cochée et que le champ de saisie n'est pas visible :

Case décochée, à côté du texte indiquant « Show Room Controls » (Afficher les commandes de salle)

De plus, lorsque vous tentez de cocher la case, cela n'a aucun effet sur l'interface utilisateur pour l'instant. Ajoutez le code ci-dessous juste sous le bloc useEffect que vous avez écrit précédemment :

  useEffect(() => {
    if (process.env.REACT_APP_SOCKET_URL) {
      const socket = io(process.env.REACT_APP_SOCKET_URL, { transports: ['websocket'] });

      setSocket(socket);
    }
  }, []);

  // Show or hide the controls when a user checks the checkbox.
  const onCheckboxChange = () => {
    setShowControls(!showControls);
  };

Mettez à jour l'entrée de Show Room Controls pour appeler onCheckboxChange lorsqu'un utilisateur essaie de cocher la case :

    <div className="app">
      <label className="start">
        <input
          type="checkbox"
          checked={showControls}
          onChange={onCheckboxChange}/>
        Show Room Controls
      </label>

Maintenant, si vous regardez l'interface utilisateur dans votre navigateur et cliquez sur la case à cocher, vous pouvez voir le champ de saisie et le bouton Create Room (Créer une salle) :

Case cochée avec le texte « Show Room Controls » (Afficher les commandes de salle). Champ de saisie avec l&#x27;étiquette « Name your room » (Nommer votre salle). Bouton rose intitulé « Create Room » (Créer une salle).

Cependant, avant de créer une nouvelle salle vidéo, vous devez pouvoir afficher toutes les salles déjà créées. Pour ce faire, ajoutez une fonction qui répertorie ces salles vidéo.

Juste en dessous de votre fonction onCheckboxChange, ajoutez le code suivant pour la fonction listRooms qui appellera le point de terminaison /rooms de votre serveur :

  // List all of the available main rooms
  const listRooms = async () => {
    try {
      const response = await fetch('http://localhost:5000/rooms/', {
        method: 'GET',
        headers: {
          'Content-Type': 'application/json',
        },
      });

      const data = await response.json();
      setRoomList(data.rooms);

    } catch (err) {
      console.log(err);
    }
  };

Avec ce code, vous pouvez récupérer la liste des salles vidéo actives à partir du serveur et les enregistrer dans l'état du composant en tant que roomList.

Lorsqu'un utilisateur accède pour la première fois à cette page, cette fonction doit être appelée immédiatement lors du chargement, afin que l'utilisateur puisse voir quelles salles sont disponibles. Pour ce faire, mettez à jour le hook useEffect que vous avez créé précédemment de sorte à appeler listRooms lorsque le composant s'affiche pour la première fois :

  useEffect(() => {
    if (process.env.REACT_APP_SOCKET_URL) {
      const socket = io(process.env.REACT_APP_SOCKET_URL, { transports: ['websocket'] });

      setSocket(socket);
    }
    listRooms();
  }, []);

Il est temps d'ajouter des fonctions permettant de créer une nouvelle salle principale et une nouvelle salle de groupe. Ajoutez ces deux fonctions sous votre code pour listRoom() :

   // Create a new main room
  const createRoom = async () => {
    try {
      const response = await fetch('http://localhost:5000/rooms/main', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          roomName: newRoomName
        }),
      });

      await response.json();

      // Once the new room is created, set this input field to be blank
      setNewRoomName('');

    } catch (err) {
      console.log(err);
    }
  };

  // Create a new breakout room
  const createBreakoutRoom = async () => {
    // For now, disallow creating nested breakout rooms.
    // If the current room isn't a main room, don't let a new breakout be created.
    if (!roomList.find(mainRoom => room?.sid === mainRoom._id)) {
      console.log('Creating nested breakout rooms is not yet implemented.');
      return;
    }

    try {
      const response = await fetch('http://localhost:5000/rooms/breakout', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({roomName: newRoomName, parentSid: room?.sid}),
      });

      await response.json();
      setNewRoomName('');

    } catch (err) {
      console.log(err);
    }
  };

Avec ce code, le nom de la salle entré par un utilisateur dans le champ de saisie sera utilisé pour créer la nouvelle salle vidéo côté serveur. Une fois la salle créée, les détails de cette salle seront renvoyés côté client.

Pour utiliser ces fonctions, mettez à jour l'élément <button> de sorte à appeler la fonction createRoom pour une salle principale ou, si un appel vidéo est déjà en cours, appelez createBreakoutRoom afin de créer une nouvelle salle de groupe :

          <button disabled={newRoomName === '' ? true : false} onClick={room ? createBreakoutRoom : createRoom}>
            {room ? 'Create Breakout Room' : 'Create Room'}
          </button>

Maintenant, ajoutez du code à ce composant pour afficher les salles vidéo créées. Au-dessus de l'élément de fin <div>, ajoutez le code suivant :

      <div className='video-rooms-list'>
        { room == null && roomList.length > 0 &&
          <h3>Video Rooms - Click a button to join</h3>
        }
        { room == null &&
          roomList.map((room) => {
            return <button disabled={identity === '' ? true : false}
                          key={room._id}>
                      {room.name}
                    </button>
          })
        }
      </div>

Avant d'essayer de créer une salle vidéo, vous devez mettre à jour une autre section. Vous vous souvenez que vous avez ajouté du code pour émettre des événements Socket.IO lors de la création d'une salle ? Il est temps d'ajouter du code pour permettre à votre côté client de répondre à ces événements.

À l'intérieur du bloc useEffect que vous avez ajouté précédemment, ajoutez des lignes pour savoir quand les événements Main room created et Breakout room created ont lieu. Une fois que quelqu'un a créé une salle à partir de votre application React, demandez à l'application de rappeler listRooms pour récupérer la liste la plus récente des salles vidéo. Ici, incluez également une fonction de nettoyage qui supprimera les processus d'écoute lorsque le composant sera sur le point d'être démonté :

  useEffect(() => {
    if (process.env.REACT_APP_SOCKET_URL) {
      const socket = io(process.env.REACT_APP_SOCKET_URL, { transports: ['websocket'] });

      // Listen for events
      socket.on('Main room created', () => listRooms());
      socket.on('Breakout room created', () => listRooms());

      setSocket(socket);
    }

    listRooms();

    // Clean up the listeners when the component is about to unmount.
    return () => {
      if (socket) {
        socket.off('Main room created')
        socket.off('Breakout room created')
      }
    }
  }, []);

Vous disposez maintenant de suffisamment de code pour essayer de créer une salle vidéo. Essayez de créer une salle nommée « Music » en saisissant ce nom dans le champ de saisie, puis en cliquant sur le bouton Create Room (Créer une salle). Une fois que vous avez terminé, un bouton s'affiche avec le nom de cette nouvelle salle ! Le bouton restera désactivé jusqu'à une prochaine étape, mais vous vous rapprochez de votre objectif.

Le texte « Video Rooms - Click a button to join » (Salles vidéo - Cliquez sur un bouton pour rejoindre la salle) apparaît au-dessus d&#x27;un nouveau bouton rose intitulé « Music ».

Quel plaisir de pouvoir créer des salles vidéo !

Ajouter des fonctions permettant aux utilisateurs de rejoindre et de quitter une salle vidéo

Maintenant que vous pouvez créer des salles vidéo, il est temps d'ajouter du code pour permettre aux utilisateurs de rejoindre l'une de ces salles.

Tout d'abord, mettez à jour le composant afin d'inclure un moyen pour l'utilisateur de saisir son identité. Dans cet exemple de projet, l'identité est tout simplement le nom d'un utilisateur qui s'affiche avec sa vidéo pendant le chat. Ajoutez le code suivant juste au-dessus de <div className='video-rooms-list'> :

      { room === undefined
        ? <div className="start">
            <input
              value={identity}
              onChange={(event) => {
                setIdentity(event.target.value);
              }}
              placeholder="Enter your name" />
          </div>
        : <div>Room</div>
      }

Avec ce code, si aucune salle vidéo n'est en cours (room === undefined), l'interface utilisateur affiche l'entrée permettant à l'utilisateur de saisir son nom.

Pour l'instant, le code où se trouve la salle vidéo n'est qu'un espace réservé (<div>Room</div>). Vous ajouterez le code correspondant à cela lors d'une étape ultérieure.

Maintenant que vous pouvez obtenir le nom de l'utilisateur, juste en dessous de la fonction createBreakoutRooms, ajoutez une fonction nommée joinRoom qui connecte l'utilisateur à la salle vidéo et commence à afficher sa vidéo :

  // Join a video room
  const joinRoom = async (roomSid: string, breakout: boolean = false) => {
    try {
      // If you're already in another video room, disconnect from that room first
      if (room) {
        await room.disconnect();
      }

      // Fetch an access token from the server
      const response = await fetch('http://localhost:5000/token', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          identity,
          roomSid
        }),
      });

      const data = await response.json();

      // Connect to the video room
      const videoRoom = await connect(data.accessToken, {
        audio: true,
        video: { width: 640, height: 480 }
      });

      // Save this video room in the state
      setRoom(videoRoom);
      if (!breakout) setParentSid(videoRoom.sid);

    } catch (err) {
      console.log(err);
    }
  };

Cette fonction appelle le point de terminaison /token que vous avez créé côté serveur. Lorsqu'un utilisateur clique sur un bouton pour rejoindre une salle, l'application récupère un token d'accès lui permettant d'accéder à cette salle. Pour ce faire, mettez à jour l'élément <button> pour les salles dans roomList afin d'appeler joinRoom lorsque vous cliquez sur un bouton :

        { room == null &&
          roomList.map((room) => {
            return <button disabled={identity === '' ? true : false}
                          key={room._id}
                          onClick={() => (joinRoom(room._id))}>
                      {room.name}
                    </button>
          })
        }

Maintenant que vous avez créé un moyen de rejoindre des salles pour les utilisateurs, vous devez également créer une fonction leur permettant de quitter les salles. Après tout, vous ne voudriez pas que votre utilisateur reste coincé à jamais dans une salle vidéo, n'est-ce pas ?

Pour créer la fonction leaveRoom, ajoutez le code suivant sous votre fonction joinRoom() :

   // Leave a video room
  const leaveRoom = async () => {
    if (room) {
      // Detach and remove all the tracks
      room.localParticipant.tracks.forEach(publication => {
        if (publication.track.kind === 'audio' || publication.track.kind === 'video') {
          publication.track.stop();
          const attachedElements = publication.track.detach();
          attachedElements.forEach(element => element.remove());
        }
      });

      room.disconnect();
      setRoom(undefined);
    }
  };

Avec cette fonction, lorsqu'un utilisateur quitte une salle vidéo, les sons et vidéos partagés avec les autres personnes de la salle sont arrêtés et l'utilisateur est déconnecté de la salle.

Quel plaisir de pouvoir créer des salles vidéo ! Cependant, pour commencer à partager le son et la vidéo dans ces salles, vous devez travailler sur les composants Room.tsx et Participant.tsx. Nous allons voir cela maintenant.

Mettre à jour le composant Room

Ouvrez le fichier src/Room.tsx dans votre éditeur de code. Dans ce fichier, ajoutez le code ci-dessous pour mettre à jour les instructions d'importation et créer une interface pour les propriétés que vous allez transmettre au composant Room :

import React, { useEffect, useState } from 'react';
import { Room as VideoRoom } from 'twilio-video';
import { BreakoutRoom } from './App';

interface RoomProps {
  room: VideoRoom;
  breakoutRoomList: BreakoutRoom[];
  parentSid: string;
  joinRoom: (roomSid: string, breakout: boolean) => void;
  leaveRoom: () => void;
}

Ensuite, remplacez le composant Room vide par le code ci-dessous :

const Room = ({ room, breakoutRoomList, parentSid, joinRoom, leaveRoom }: RoomProps) => {

  const changeRoom = async (sid: string, returnToMain: boolean = false) => {
    // Disconnect fully from the room you're in currently before joining the next one
    await leaveRoom();

    if (returnToMain) {
      return joinRoom(parentSid, false);
    }
    return joinRoom(sid, true);
  }

  return (
    <div className="room">
      <h2 className="roomName">{room.name}</h2>
      <div className="participants">

      </div>
      <div className='breakouts-list'>
        { breakoutRoomList.length > 0 &&
          <h3>Breakout Rooms</h3>
        }

        { breakoutRoomList.map((room) => {
            return <button className="breakout" key={room._id} onClick={() => changeRoom(room._id, false)}>{room.name}</button>
          })
        }

      </div>
      { room.sid !== parentSid &&
        <button onClick={() => changeRoom(parentSid, true)}>Return to Main Room</button>
      }
      <button onClick={leaveRoom}>Leave Video Call</button>
    </div>
  );
}

export default Room;

Ce composant affiche le nom de la salle et des éventuelles salles de groupe qu'elle a. En outre, la fonction changeRoom() permet à l'utilisateur de basculer vers une autre salle de groupe. S'il clique sur le bouton Leave Video Call (Quitter l'appel vidéo), il peut quitter complètement l'appel.

Avant d'afficher les participants dans votre application vidéo, vous devez mettre à jour le composant Participant.tsx.

Mettre à jour le composant Participant

Ouvrez le fichier src/Participant.tsx dans votre éditeur de code. En haut du fichier, ajoutez le code ci-dessous pour mettre à jour les importations et créer une interface pour les propriétés que vous allez transmettre à ce composant :

import React, { useState, useEffect, useRef } from 'react';
import { AudioTrack, VideoTrack, Participant as VideoParticipant, } from 'twilio-video';

interface ParticipantProps {
  participant: VideoParticipant;
}

Remplacez ensuite le composant Participant vide par le code ci-dessous. Consultez les commentaires de chaque partie pour voir ce que fait le code :

const Participant = ({ participant }: ParticipantProps) => {

  const [videoTracks, setVideoTracks] = useState<(VideoTrack | null)[]>([]);
  const [audioTracks, setAudioTracks] = useState<(AudioTrack | null)[]>([]);

  // Create refs for the HTML elements to attach audio and video to in the DOM
  // For now, set them to null
  const videoRef = useRef<HTMLVideoElement>(null);
  const audioRef = useRef<HTMLMediaElement>(null);

  // Get the audio and video tracks from the participant, filtering out the tracks that are null
  const getExistingVideoTracks  = (participant: VideoParticipant) => {
    const videoPublications = Array.from(participant.videoTracks.values());
    const existingVideoTracks = videoPublications.map(publication => publication.track).filter(track => track !== null);
    return existingVideoTracks;
  }

  const getExistingAudioTracks = (participant: VideoParticipant) => {
    const audioPublications = Array.from(participant.audioTracks.values());
    const existingAudioTracks = audioPublications.map(publication => publication.track).filter(track => track !== null);
    return existingAudioTracks;
  }

  // When a new track is added or removed, update the video and audio tracks in the state
  useEffect(() => {
    const trackSubscribed = (track: AudioTrack | VideoTrack) => {
      if (track.kind === 'video') {
        setVideoTracks(videoTracks => [...videoTracks, track]);
      } else {
        setAudioTracks(audioTracks => [...audioTracks, track]);
      }
    };

    const trackUnsubscribed = (track: AudioTrack | VideoTrack) => {
      if (track.kind === 'video') {
        setVideoTracks(videoTracks => videoTracks.filter(v => v !== track));
      } else {
        setAudioTracks(audioTracks => audioTracks.filter(a => a !== track));
      }
    };

    setVideoTracks(getExistingVideoTracks(participant));
    setAudioTracks(getExistingAudioTracks(participant));

    // Set up event listeners
    participant.on('trackSubscribed', trackSubscribed);
    participant.on('trackUnsubscribed', trackUnsubscribed);


    // Clean up at the end by removing all the tracks and the event listeners
    return () => {
      setVideoTracks([]);
      setAudioTracks([]);
      participant.removeAllListeners();
      participant.videoTracks.forEach((track) => track.isEnabled = false)
    };
  }, [participant]);

  // When a new videoTrack or audioTrack is subscribed, add it to the DOM. 
  // When unsubscribed, detach it
  useEffect(() => {
    const videoTrack = videoTracks[0];
    if (videoRef && videoRef.current) {
      if (videoTrack) {
        videoTrack.attach(videoRef.current);
        return () => {
          videoTrack.detach();
        };
      }
    }
  }, [videoTracks]);

  useEffect(() => {
    const audioTrack = audioTracks[0];
    if (audioRef && audioRef.current) {
      if (audioTrack) {
        audioTrack.attach(audioRef.current);
        return () => {
          audioTrack.detach();
        };
      }
    }
  }, [audioTracks]);


  return (
    <div className="participant" id={ participant.identity }>
      <div className="identity">{ participant.identity }</div>
      <video ref={videoRef} autoPlay={true} />
      <audio ref={audioRef} autoPlay={true} muted={true} />
    </div>
  );
};

export default Participant;

Dans ce composant, vous joignez les éléments audio et vidéo d'un participant, et vous affichez l'identité du participant. Ainsi, chaque participant au chat vidéo saura qui est qui.

Rassembler tous les éléments

Maintenant que vous disposez d'un composant qui affiche la vidéo et le son de chaque participant, il est temps de rassembler tous les éléments de cette application.

Revenez au fichier Room.tsx dans votre éditeur de code. En haut du fichier, importez le composant Participant sur lequel vous venez de travailler à l'étape précédente :

import React, { useEffect, useState } from 'react';
import { Room as VideoRoom } from 'twilio-video';
import { BreakoutRoom } from './App';
import Participant from './Participant';

Ajoutez ensuite le code ci-dessous dans le composant Room juste au-dessus de la fonction changeRoom :

  const [remoteParticipants, setRemoteParticipants] = useState(Array.from(room.participants.values()));

  // Whenever the room changes, set up listeners
  useEffect(() => {
    room.on('participantConnected', (participant) => {
      console.log(`${participant.identity} has entered the chat`);
      setRemoteParticipants(prevState => ([...prevState, participant]));
    });
    room.on('participantDisconnected', (participant) => {
      console.log(`${participant.identity} has left the chat`);
      setRemoteParticipants(prevState => prevState.filter(p => p.identity !== participant.identity));
    });
  }, [room]);

Dans le code ci-dessus, vous stockez les participants à distance dans l'état du composant et vous configurez des processus d'écoute d'événements pour savoir à chaque fois qu'un participant rejoint ou quitte la salle.

Enfin, pour ce composant, mettez à jour l'instruction de retour pour remplir la partie <div> des participants actuellement vide qui affichera chaque participant dans la salle vidéo, y compris leurs noms, leur audio et leur vidéo :

      <div className="participants">
        <Participant
          key={room.localParticipant.identity}
          participant={room.localParticipant} />
        { remoteParticipants.map((participant) =>
            <Participant
              key={participant.identity}
              participant={participant} />
          )
        }
      </div>

Ce composant est désormais prêt à l'emploi. Il vous suffit d'apporter quelques modifications au fichier App.tsx avant de pouvoir tester votre application vidéo.

Dans le fichier App.tsx, ajoutez le composant Room à votre liste d'importations :

import React, { useEffect, useState } from 'react';
import { connect, Room as VideoRoom } from 'twilio-video';
import './App.css';
import io, { Socket } from 'socket.io-client';
import Room from './Room';

Juste en dessous de la fonction pour leaveRoom, ajoutez une fonction d'assistance qui permet d'accéder aux salles de groupe de la salle principale en cours :

  const getBreakoutRooms = () => {
    // Select the current room from the roomList and return its breakout rooms.
    if (room) {
      const roomInfo = roomList.find((mainRoom) => mainRoom._id === room.sid);
      if (roomInfo) {
        return roomInfo.breakouts;
      }
    }

    // If there are no breakout rooms, return an empty array.
    return [];
  }

Enfin, dans l'instruction de retour, remplacez <div>Room</div> par le composant Room, en transmettant les propriétés nécessaires :

     { room === undefined
          ? <div className="start">
              <input
                value={identity}
                onChange={(event) => {
                  setIdentity(event.target.value);
                }}
                placeholder="Enter your name" />
            </div>
          : <Room room={room}
                  joinRoom={joinRoom}
                  leaveRoom={leaveRoom}
                  breakoutRoomList={getBreakoutRooms()}
                  parentSid={parentSid} />
      }

Votre application est terminée !

Tester l'application

C'est le moment idéal pour tester votre application et la voir en action.

Assurez-vous que la case Show Room Controls (Afficher les commandes de salle) est cochée. Saisissez ensuite un nom pour la salle que vous souhaitez créer, puis cliquez sur Create Room (Créer une salle). Une fois cette opération effectuée, un bouton permettant de rejoindre la nouvelle salle vidéo s'affiche à l'écran :

Sous les entrées du nom de la salle et du nom de l&#x27;utilisateur, un bouton désactivé violet clair intitulé « Artists Chat » s&#x27;affiche.

Comme vous pouvez le voir, j'ai nommé ma salle « Artists Chat », mais vous pouvez choisir le nom que vous voulez.

Vous remarquerez que le bouton de votre nouvelle salle est désactivé. Cela est dû au fait que vous n'avez pas encore saisi votre nom dans la zone de saisie de l'identité. Entrez votre nom dans la zone de saisie du nom. Vous verrez que le bouton a changé de couleur et indique qu'il est actif :

Le bouton « Artists Chat » est maintenant actif, comme le montre le changement de couleur au violet foncé.

Maintenant, cliquez sur le bouton portant le nom de votre salle pour rejoindre l'appel vidéo. Votre vidéo s'affiche à l'écran. Vous remarquerez également que le texte de Create Room (Créer une salle) a été remplacé par Create Breakout Room (Créer une salle de groupe). Le bouton que vous avez créé pour Leave Video Call (Quitter l'appel vidéo) est également visible :

Le nom de l&#x27;appel vidéo, « Artists Chat », apparaît en gras. La vidéo ci-dessous montre l&#x27;auteure, Mia, souriante.

Il est temps d'essayer de créer des salles de groupe ! Saisissez le nom de la salle de groupe que vous souhaitez créer, puis cliquez sur le bouton Create Breakout Room (Créer une salle de groupe).

Pour mon application, étant donné que j'ai créé un chat d'artistes, j'ai ajouté plusieurs salles pour les différents types d'artistes qui pourraient participer à l'appel :

Sous la vidéo se trouvent 5 boutons orange correspondant aux salles de groupe : Bassists, Guitarists, Singers, Filmmakers et Illustrators.

Comme vous pouvez le voir, j'ai créé cinq salles de groupe différentes et leurs noms apparaissent en orange.

Cliquez sur l'un des boutons de salle de groupe pour accéder à la salle correspondante. Donc, si je clique sur la salle pour les Bassists, je peux rejoindre cette discussion avec ma guitare basse :

Le titre de la salle est désormais « Bassists ». L&#x27;auteure sourit devant la caméra en tenant une guitare basse.

Lorsque vous entrez dans une salle de groupe, vous voyez que le nom de la salle actuelle a changé. En outre, le bouton Return to Main Room (Retour à la salle principale) est désormais visible. Vous pouvez cliquer dessus pour revenir à la plus grande salle.

Pour voir comment cela fonctionne avec plusieurs participants, vous pouvez accéder à http://localhost:3000/ dans un deuxième onglet du navigateur et rejoindre le chat vidéo avec une identité différente. Dans l'exemple ci-dessous, j'ai rejoint Artists Chat, puis j'ai rejoint la salle de groupe Bassists en tant que « Another Bassist » (Autre bassiste) :

Dans la salle « Bassists », il y a maintenant deux participants : « Mia » et « Another Bassist ».

Vous pouvez essayer de rejoindre et de quitter des salles de groupe sous deux identités différentes et voir à quoi ressemble l'expérience. Pas mal, n'est-ce pas ?

Quelle est la prochaine étape pour votre application vidéo avec des salles de groupe ?

Maintenant que vous disposez d'une application vidéo où vous pouvez créer des salles de groupe, qu'allez-vous construire ? Vous souhaiterez peut-être essayer de construire une application vidéo avec une fonction de chat textuel ? Ou peut-être, ajouter une fonctionnalité permettant à vos participants d'activer ou de désactiver le son ?

Si vous souhaitez extraire l'intégralité du code de ce tutoriel, consultez la branche updated-client de ce répertoire GitHub.

Elle recèle d'idées géniales. J'ai hâte de voir ce que vous allez construire !

Mia Adjei est développeuse de logiciels au sein de l'équipe Developer Voices. Son rôle consiste à aider les développeurs à créer de nouveaux projets et à connaître de grands moments de satisfaction. Vous pouvez contacter Mia à l'adresse : madjei [at] twilio.com.