https://d226lax1qjow5r.cloudfront.net/blog/blogposts/waiting-room-and-pre-call-best-practices-with-vonage-video-api/waitingroom_videoapi_1200x600.png

Pratiques exemplaires pour les salles d'attente et les appels préalables grâce à l'API Video de Vonage

Publié le July 13, 2021

Temps de lecture : 8 minutes

Lorsque vous développez votre propre solution de vidéoconférence, il est essentiel d'offrir une bonne expérience avant l'appel. Assurez-vous que l'utilisateur peut choisir les périphériques audio et Video à utiliser, que le microphone détecte votre voix et que la puissance de votre réseau est suffisante. En vérifiant tous ces points, vous pourrez créer une application plus robuste et détecter certains problèmes qui, espérons-le, réduiront les frictions avec les utilisateurs finaux de votre application.

Il est également très courant ces jours-ci d'avoir différents rôles dans votre application ; si vous opérez dans le domaine de la santé, de l'éducation ou de l'espace webinaire, vous voudrez peut-être avoir un modérateur. Dans cet article de blog, nous verrons comment faire en sorte que les autres participants attendent que le modérateur rejoigne la session avant de commencer à publier.

En résumé, cet exemple d'application met en œuvre :

Modération. Attendez que le modérateur/l'hôte commence à publier dans la session. Sélection de l'appareil. Meilleures pratiques avant l'appel. Il s'agit notamment d'un test de connectivité et de qualité avant l'appel et d'autres bonnes pratiques telles que les indicateurs de niveau audio.

Si cela vous tente, restez dans les parages. Si vous vous sentez un peu paresseux et que vous voulez le faire, vous pouvez voir le dépôt dépôt terminé ici.

Structure du projet

Côté serveur

Le fichier fichier principal du serveur est un serveur express Node.js de base qui sert deux fichiers HTML en fonction de la route choisie (/host ou /participant). Le serveur est également chargé de générer les informations d'identification pour la session (jeton et ID de session).

Le serveur génère soit un jeton de modérateur pour l'hôte, soit un jeton d'éditeur pour le participant. Pour plus d'informations sur la création de jetons, consultez ce lien. Le serveur stocke en mémoire une carte des sessions et des roomNames en mémoire. Pour une application de production, vous devrez stocker ces sessions dans une base de données ou autre.

Côté client

L'application utilise Webpack pour regrouper tous les fichiers JavaScript et rendre l'application plus évolutive et plus facile à comprendre. Elle utilise également Bootstrap pour simplifier le processus de conception de l'interface utilisateur.

Tous les fichiers JavaScript se trouvent dans le dossier dossier src:

Le point d'entrée principal est index.js dans le dossier src. Ce fichier récupère le roomName de l'URL et, en fonction de la route visitée, créera une instance de Host ou de Participant. Il initialisera ensuite le processus.

import { Host } from "./Host";
import { Participant } from "./Participant";

(() => {
  const urlParams = new URLSearchParams(window.location.search);
  const roomName = urlParams.get("room");
  if (window.location.pathname === "/host") {
    const host = new Host(roomName);
    host.init();
  } else if (window.location.pathname === "/participant") {
    const participant = new Participant(roomName);
    participant.init();
  }
})();

La logique de l'application se déroule dans la classe hôte et dans la classe des participants en fonction du rôle de l'utilisateur connecté. Ces classes exploitent des fichiers supplémentaires pour améliorer la lisibilité du code. Vous pouvez vérifier les différents fichiers utilisés par notre application.

Sélection de l'appareil

La sélection du dispositif sera mise en œuvre dans les deux vues (participant et hôte). Comme il est expliqué dans la référence de l'API MediaDevicesnotre application doit être suffisamment robuste pour gérer la connexion/déconnexion des appareils pendant l'appel.

Que se passe-t-il si un appareil est débranché pendant l'appel ou si un nouvel appareil est branché ? Notre application doit être suffisamment intelligente pour détecter un changement dans la liste des appareils disponibles. Nous allons mettre en place un écouteur d'événements de sorte que lorsqu'il y a un changement dans la liste des appareils disponibles, cela déclenche un appel de fonction pour mettre à jour notre interface utilisateur afin d'afficher les derniers appareils disponibles.

Tout d'abord, nous déclencherons le premier appel pour mettre à jour notre liste d'appareils une fois que l'utilisateur aura accordé l'autorisation d'utiliser la caméra et/ou le microphone. Nous pouvons le faire en exploitant les événements accessAllowed émis par l'éditeur.

this.publisher.on("accessAllowed", () => {
  refreshDeviceList(this.publisher);
});

Ensuite, nous mettrons en place un récepteur d'événements qui recalculera les appareils disponibles en cas de mise à jour des appareils multimédias disponibles au cours de l'appel.

navigator.mediaDevices.ondevicechange = () => {
  refreshDeviceList(this.publisher);
};

La fonction refreshDeviceList est chargée d'ajouter la liste des appareils audio et vidéo à un élément DOM. Dans ce cas, j'utiliserai un menu déroulant pour plus de simplicité. Si vous voulez voir plus de détails sur cette fonction, n'hésitez pas à consulter le code source de l'implémentation de la fonction out. Nous allons également ajouter une balise HTML selected aux sources audio et vidéo actuelles retournées par getAudioSource() et getVideoSource respectivement.

Lorsqu'il s'agit de gérer le changement d'appareil au cours de l'appel, nous utiliserons les fonctions setVideoSource et setAudioSource respectivement. Je vais ajouter ici le processus pour l'un d'entre eux afin que vous puissiez mieux le comprendre.

const onVideoSourceChanged = async (event, publisher) => {
  const labelToFind = event.target.value;
  const videoDevices = await listVideoInputs();
  const deviceId = videoDevices.find((e) => e.label === labelToFind)?.deviceId;

  if (deviceId != null) {
    publisher.setVideoSource(deviceId);
  }
};

Nous mettrons en place un écouteur d'événement lors de la modification de notre menu déroulant qui déclenchera la fonction onVideoSourceChanged qui déclenchera la fonction Cette fonction recherchera l'identifiant de l'appareil dont nous ciblons l'étiquette. Ensuite, elle appellera la méthode setVideoSource de l'objet publisher pour modifier la source vidéo.

document.getElementById("audioInputs").addEventListener("change", (e) => {
  onAudioSourceChanged(e, this.waitingRoompublisher);
});

Attendre l'hôte

Notre application doit savoir si l'utilisateur qui se joint à l'appel est un hôte ou un participant. Dans ce cas, je sers un fichier HTML différent du côté du serveur en fonction du rôle de l'utilisateur puisque notre hôte pourra déconnecter tous les participants de l'appel. Notre point d'entrée instanciera un hôte ou un participant en fonction de l'URL vers laquelle nous naviguons. Veuillez garder à l'esprit qu'il ne s'agit pas d'une application prête pour la production et que vous devez mettre en œuvre l'authentification sur les routes.

Toute la logique commence avec notre init fonction index.js dans le dossier /src qui sera exécutée soit sur une instance d'hôte, soit sur une instance de participant, en fonction de l'endroit où nous naviguons.

La fonction init appellera notre getCredentials fonction credentials.js avec un rôle défini sur admin pour l'hôte ou sur Participant pour le participant.

const getCredentials = async (roomName, role) => {
  try {
    const url = `/api/room/${roomName}?role=${role}`;
    const config = {};
    const response = await fetch(`${url}`, config);
    const data = await response.json();
    if (data.apiKey && data.sessionId && data.token) {
      return Promise.resolve(data);
    }
    return Promise.reject(new Error("Credentials Not Valid"));
  } catch (error) {
    console.log(error.message);
    return Promise.reject(error);
  }
};

Notre serveur génère alors un jeton de modérateur pour l'administrateur/l'hôte ou un jeton d'éditeur pour le participant. Pour plus d'informations sur la création de jetons et les rôles, veuillez consulter notre documentation sur la création de jetons.

Jetez un coup d'œil à la génération de jetons côté serveur.

Une fois le jeton reçu côté client, nous pouvons savoir si un hôte ou un participant se connecte à la session en écoutant les événements de connexion envoyés par notre SDK. événements de connexion envoyés par notre SDK. Le flux de l'application se déroule comme suit :

Si un hôte rejoint l'appel, il se connectera à la session et commencera à publier immédiatement. Si un participant se joint à l'appel, nous effectuerons un test préalable à l'appel, puis le participant se connectera à la session. Si un hôte est déjà connecté à la session, le participant commencera à publier. Dans le cas contraire, le participant restera connecté jusqu'à ce qu'un hôte se joigne à la session et ne commencera à publier qu'à ce moment-là.

Participant

Voici à quoi ressemble la fonction init de notre participant. Nous obtenons d'abord les informations d'identification d'un participant (rôle d'éditeur). Nous demandons également des informations d'identification distinctes pour notre test de pré-appel afin d'éviter que la session principale ne soit polluée par des connexions/flux provenant du test de pré-appel. Nous démarrons ensuite le test de pré-appel (je vous expliquerai comment faire dans une minute) et une fois le test terminé, nous nous connectons à la session.

init() {
  getCredentials(this.roomName, 'participant')
    .then(data => {
      this.roomToken = data.token;
      this.initializeSession(data);
      getCredentials(`${this.roomName}-precall`, 'participant').then(
        precallCreds => {
          startTest(precallCreds)
            .then(results => {
              this.precallTestDone = true;
              this.connect();
            })
            .catch(e => console.log(e));
        }
      );
      this.registerEvents();
    })
    .catch(e => console.log(e));
}

La fonction de connexion vérifie si un hôte est déjà connecté à la session ou non. S'il y a déjà un hôte, nous commencerons à publier, sinon nous resterons connectés.

connect() {
  this.session.connect(this.roomToken, error => {
    if (error) {
      handleError(error);
    } else {
      if (isHostPresent()) {
        this.handlePublisher();
      }
      console.log('Session Connected');
    }
  });
}

La fonction isHostPresent renvoie un message vrai si un hôte est connecté à la session et un message faux dans le cas contraire.

const isHostPresent = () => {
  if (usersConnected.find((e) => e.data === "admin")) {
    return true;
  } else {
    return false;
  }
};

Le tableau usersConnected gardera la trace des connexions dans la session. Nous l'incrémenterons lors d'un événement connectionCreated et le décrémenter lors d'un événement connectionDestroyed événement. Il est important de noter que cette variable sera incrémentée par les deux classes (hôte et participant) lorsqu'il y aura une nouvelle connexion. Nous aurons donc besoin que cette variable soit accessible par les deux classes.

this.session.on("connectionCreated", (event) => {
  connectionCount += 1;
  console.log("[connectionCreated]", connectionCount);
  usersConnected.push(event.connection);
  console.log(usersConnected);
  if (event.connection.data === "admin") {
    this.handlePublisher();
  }
});
this.session.on("connectionDestroyed", (event) => {
  connectionCount -= 1;
  console.log("[connectionDestroyed]", connectionCount);
  usersConnected = usersConnected.filter((connection) => {
    return connection.id != event.connection.id;
  });
  connectionCount -= 1;
  console.log(usersConnected);
});

Si l'hôte n'est pas présent lorsque le participant se connecte à la session, nous attendrons qu'un nouvel hôte rejoigne la session et commencerons alors à publier.

Test préalable à l'appel

Un autre aspect important de la qualité de l'expérience client est l'exécution d'un contrôle de connectivité et de qualité pour s'assurer que les choses se déroulent aussi bien que possible. Si les participants doivent attendre que le modérateur les rejoigne, pourquoi ne pas utiliser ce temps précieux pour effectuer un test préalable à l'appel ?

Nous utiliserons le test de réseau pour vérifier que le participant dispose d'une connectivité avec les serveurs de journalisation, de messagerie, de médias et d'API Video de Vonage, ainsi que pour vérifier la qualité attendue pendant l'appel. Veuillez garder à l'esprit que le comportement du réseau est dynamique, ce qui signifie qu'un résultat positif avant l'appel ne garantit pas que votre bande passante disponible ne changera pas pendant l'appel.

Par souci de simplicité, nous n'effectuerons un test de pré-appel que sur nos participants et non sur l'hôte. Vous pouvez, bien entendu, effectuer ce test sur les deux.

J'ai créé quelques fichiers pour gérer la réponse du test de connectivité et de qualité ainsi qu'une barre de progression pour indiquer l'état d'avancement du test. Il s'agit d'une simple barre de progression de Bootstrap qui se remplit après 30 secondes, ce qui correspond approximativement au temps nécessaire pour que le test se termine. Vous pouvez modifier cette durée en définissant un délai d'attente lors de l'instanciation du NetworkTest. Cependant, plus le test est long, plus les résultats seront précis. Si le test échoue, nous supprimerons également l'indicateur de progression.

const handleTestProgressIndicator = () => {
  const progressIndicator = setInterval(() => {
    let currentProgress = progressBar.value;
    progressBar.value += 3.3;
    if (currentProgress === 100) {
      clearInterval(progressIndicator);
      progressBar.value = 0;
      progressBar.style.display = "none";
    }
  }, 1000);
};
const removeProgressIndicator = () => {
  progressBar.style.display = "none";
};

Si vous voulez jeter un coup d'œil à la mise en œuvre du test de réseau, consultez le fichier où je gère la logique de pré-appel. le fichier où je gère la logique de pré-appel.

Les résultats des tests préalables à l'appel fournissent également une résolution recommandée et un score MOS de 0 à 4,5.

Étant donné que cela est un peu subjectif, nous ajouterons la possibilité de décider si nous voulons afficher la résolution préférée et une étiquette de résultat basée sur le score MOS, c'est-à-dire (Bon, Mauvais, Excellent...).

Vous pouvez décider d'inclure ou non la résolution recommandée et l'étiquette du score en activant la variable addFeedback dans le fichier /src/variables.js. Vous pouvez également utiliser le module ErrorNames du module npm pour ajouter vos propres erreurs en fonction de l'erreur lancée et ajouter des recommandations aux utilisateurs.

Quelle est la prochaine étape ?

Le projet achevé est disponible sur GitHubet vous pouvez en savoir plus sur l'API Video de Vonage en consultant notre documentation.

Partager:

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.