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

Bewährte Praktiken für Wartezimmer und Vorgespräche mit der Video API von Vonage

Zuletzt aktualisiert am July 13, 2021

Lesedauer: 7 Minuten

Wenn Sie Ihre eigene Videokonferenzlösung entwickeln, ist es wichtig, dass Sie vor dem Anruf eine gute Erfahrung machen. Stellen Sie sicher, dass der Benutzer die zu verwendenden Audio- und Videogeräte auswählen kann, prüfen Sie, ob das Mikrofon Ihre Sprache erkennt und ob die Netzstärke ausreichend ist. Die Überprüfung all dieser Punkte wird Ihnen helfen, eine robustere Anwendung zu entwickeln und einige Probleme zu erkennen, die hoffentlich zu weniger Reibungsverlusten bei den Endnutzern Ihrer Anwendung führen werden.

Heutzutage ist es auch sehr üblich, verschiedene Rollen in Ihrer Anwendung zu haben; wenn Sie im Gesundheits-, Bildungs- oder Webinarbereich tätig sind, möchten Sie vielleicht einen Moderator haben. In diesem Blog-Beitrag erfahren Sie, wie Sie den Rest der Teilnehmer dazu bringen können, auf den Moderator zu warten, bis er der Sitzung beitritt und die Veröffentlichung beginnt.

Zusammenfassend lässt sich sagen, dass diese Beispielanwendung Folgendes implementiert:

Moderation. Warten Sie, bis der Moderator/Gastgeber mit der Veröffentlichung in der Sitzung beginnt. Auswahl der Geräte. Bewährte Praktiken vor dem Anruf. Dazu gehört ein Test der Konnektivität und der Qualität vor dem Anruf sowie einige andere bewährte Verfahren wie z. B. die Anzeige des Audiopegels.

Wenn das nach einem Plan klingt, bleiben Sie hier. Wenn Sie sich ein bisschen faul fühlen, können Sie sich das fertige Repository hier sehen.

Projektstruktur

Server-Seite

Die Hauptdatei des Servers ist ein einfacher Node.js-Express-Server, der je nach gewählter Route (/host oder /participant) zwei HTML-Dateien bereitstellt. Der Server ist auch für die Erstellung der Anmeldeinformationen für die Sitzung (Token und Sitzungs-IDs) zuständig.

Der Server generiert entweder ein Moderator-Token für den Gastgeber oder ein Publisher-Token für einen Teilnehmer. Weitere Informationen zur Erstellung von Token finden Sie unter diesen Link. Der Server speichert eine Zuordnung von Sitzung und roomNames im Speicher. Für eine Produktionsanwendung müssen Sie diese Sitzungen in einer Datenbank oder ähnlichem speichern.

Kunden-Seite

Die Anwendung verwendet Webpack, um alle JavaScript-Dateien zu bündeln und die Anwendung skalierbarer und leichter verständlich zu machen. Außerdem wird Bootstrap verwendet, um die Gestaltung der Benutzeroberfläche zu vereinfachen.

Alle JavaScript-Dateien befinden sich im Ordner src-Ordner:

Der Haupteinstiegspunkt ist index.js im Ordner src. Diese Datei holt die roomName aus der URL und erstellt je nach der besuchten Route eine Instanz von Host oder Participant. Anschließend initialisiert sie den Prozess.

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

Die Anwendungslogik findet in der Host-Klasse und in der Teilnehmer-Klasse abhängig von der Rolle des angeschlossenen Benutzers. Diese Klassen nutzen zusätzliche Dateien, um die Lesbarkeit des Codes zu verbessern. Sie können sich die verschiedenen Dateien, die unsere Anwendung verwendet.

Geräteauswahl

Die Geräteauswahl wird in beiden Ansichten (Teilnehmer und Host) durchgeführt. Wie es erklärt wird in der MediaDevices API-Referenzbeschrieben ist, muss unsere Anwendung robust genug sein, um das Anschließen und Trennen von Geräten während des Anrufs zu bewältigen.

Was passiert, wenn ein Gerät während des Anrufs ausgesteckt oder ein neues Gerät angeschlossen wird? Unsere Anwendung muss intelligent genug sein, um eine Änderung in der Liste der verfügbaren Geräte zu erkennen. Wir werden einen Ereignis-Listener einrichten, der bei einer Änderung der verfügbaren Geräte einen Funktionsaufruf auslöst, um unsere Benutzeroberfläche zu aktualisieren und die neuesten verfügbaren Geräte anzuzeigen.

Zunächst werden wir den ersten Aufruf zur Aktualisierung unserer Geräteliste auslösen, sobald der Benutzer die Erlaubnis für die Kamera und/oder das Mikrofon erteilt. Dies können wir tun, indem wir die accessAllowed Ereignisse, die vom Herausgeber ausgegeben werden.

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

Dann richten wir einen Ereignis-Listener ein, der die verfügbaren Geräte neu berechnet, wenn es während des Anrufs eine Aktualisierung der verfügbaren Mediengeräte gibt.

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

Die Funktion refreshDeviceList Funktion ist dafür zuständig, die Liste der Audio- und Videogeräte an ein DOM-Element anzuhängen. In diesem Fall werde ich der Einfachheit halber ein Dropdown-Menü verwenden. Wenn Sie mehr Details zu dieser Funktion sehen möchten, können Sie sich den Quellcode der Funktionsimplementierung aus. Wir werden auch ein HTML selected Tag zu den aktuellen Audio- und Videoquellen hinzufügen, die von getAudioSource() und getVideoSource zurückgegeben werden.

Wenn es um die Behandlung von Gerätewechseln während des Anrufs geht, werden wir die setVideoSource und setAudioSource verwenden. Ich werde hier den Prozess für einen von ihnen hinzufügen, damit Sie ihn besser verstehen.

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

Wir werden einen Ereignis-Listener für die Änderung unseres Dropdown-Menüs einrichten, der die onVideoSourceChanged Funktion auslöst. Diese Funktion sucht nach der Geräte-ID, deren Bezeichnung wir anvisieren. Dann ruft sie die setVideoSource Methode des Publisher-Objekts auf, um die Videoquelle zu ändern.

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

Warten auf den Gastgeber

Unsere Anwendung muss wissen, ob der teilnehmende Benutzer ein Gastgeber oder ein Teilnehmer ist. In diesem Fall wird je nach Rolle des Benutzers eine andere HTML-Datei auf dem Server bereitgestellt, da unser Host in der Lage sein wird, alle Teilnehmer vom Anruf zu trennen. Unser Einstiegspunkt instanziiert einen Host oder einen Teilnehmer, abhängig von der URL, zu der wir navigieren. Bitte bedenken Sie, dass dies keine produktionsreife Anwendung ist und Sie die Authentifizierung auf den Routen implementieren sollten.

Die gesamte Logik beginnt mit unserer init Funktion index.js im Ordner /src die entweder auf einer Host- oder einer Teilnehmerinstanz ausgeführt wird, je nachdem, wohin wir navigieren.

Die Funktion init Funktion ruft unsere getCredentials Funktion credentials.js mit einer Rolle, die für den Gastgeber auf admin oder für den Teilnehmer auf Participant eingestellt ist.

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

Unser Server generiert dann ein Moderator-Token für den Admin/Host oder ein Publisher-Token für den Teilnehmer. Weitere Informationen zur Erstellung von Token und Rollen finden Sie in unsere Dokumentation zur Token-Erstellung.

Werfen Sie einen Blick auf die serverseitige Token-Generierung.

Sobald das Token auf der Client-Seite empfangen wurde, können wir wissen, ob ein Host oder ein Teilnehmer sich mit der Sitzung verbindet, indem wir auf Verbindungsereignisse, die von unserem SDK gesendet werden. Der Ablauf der Anwendung gestaltet sich wie folgt:

Wenn ein Gastgeber an der Aufforderung teilnimmt, wird er mit der Sitzung verbunden und beginnt sofort mit der Veröffentlichung. Wenn ein Teilnehmer dem Anruf beitritt, führen wir einen Test vor dem Anruf durch, und dann wird der Teilnehmer mit der Sitzung verbunden. Wenn bereits ein Gastgeber mit der Sitzung verbunden ist, beginnt der Teilnehmer mit der Veröffentlichung. Andernfalls bleibt der Teilnehmer so lange verbunden, bis sich ein Gastgeber anschließt, und beginnt erst dann mit der Veröffentlichung.

Teilnehmer

So sieht die init Funktion unseres Teilnehmers aussieht. Zunächst erhalten wir Anmeldeinformationen für einen Teilnehmer (Herausgeberrolle). Wir fordern auch separate Anmeldeinformationen für unseren Voranruf-Test an, um zu verhindern, dass die Hauptsitzung durch Verbindungen/Streams vom Voranruf-Test verunreinigt wird. Dann starten wir den Voranruftest (wie das geht, erkläre ich in einer Minute), und sobald der Test abgeschlossen ist, stellen wir eine Verbindung zur Sitzung her.

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

Die Verbindungsfunktion prüft, ob bereits ein Host mit der Sitzung verbunden ist oder nicht. Wenn es bereits einen Host gibt, werden wir mit der Veröffentlichung beginnen, wenn nicht, bleiben wir verbunden.

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

Die Funktion isHostPresent Funktion gibt true zurück, wenn ein Host mit der Sitzung verbunden ist, und false, wenn nicht.

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

Das Array usersConnected Array behält den Überblick über die Verbindungen in der Sitzung. Wir erhöhen es bei einem connectionCreated Ereignis und dekrementieren es bei einem connectionDestroyed Ereignis. Es ist wichtig zu beachten, dass diese Variable von beiden Klassen (Host und Teilnehmer) inkrementiert wird, wenn es eine neue Verbindung gibt. Daher muss diese Variable für beide Klassen zugänglich sein.

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

Wenn der Host nicht anwesend ist, wenn der Teilnehmer eine Verbindung zur Sitzung herstellt, warten wir, bis ein neuer Host der Sitzung beitritt und beginnen dann mit der Veröffentlichung.

Test vor dem Anruf

Ein weiterer wichtiger Aspekt für ein gutes Kundenerlebnis ist die Überprüfung der Konnektivität und der Qualität, um sicherzustellen, dass die Dinge so reibungslos wie möglich ablaufen. Wenn die Teilnehmer darauf warten müssen, dass der Moderator sich einschaltet, warum nutzen wir diese wertvolle Zeit nicht für einen Test vor dem Anruf?

Wir verwenden den Netzwerktest npm-Modul, um zu prüfen, ob der Teilnehmer mit den Vonage Video API-Protokollierungs-, Messaging-, Medien- und API-Servern verbunden ist, und um die erwartete Qualität während des Anrufs zu prüfen. Bitte beachten Sie, dass das Verhalten des Netzwerks dynamisch ist, d. h. ein positives Ergebnis vor dem Anruf ist keine Garantie dafür, dass sich Ihre verfügbare Bandbreite während des Anrufs nicht ändert.

Der Einfachheit halber führen wir einen Vorabaufruftest nur bei unseren Teilnehmern, nicht aber beim Gastgeber durch. Sie können ihn natürlich mit beiden durchführen.

Ich habe einige Dateien erstellt, um die Antworten aus dem Konnektivitäts- und Qualitätstest zu verarbeiten, und auch einen Fortschrittsbalken, der den Status des Tests anzeigt. Es ist ein einfacher Fortschrittsbalken von Bootstrap, der sich nach 30 Sekunden füllt, was ungefähr der Zeit entspricht, die der Test braucht, um abgeschlossen zu werden. Sie können dies ändern, indem Sie bei der Instanziierung des NetworkTest einen Timeout-Wert festlegen. Je länger der Test jedoch läuft, desto genauer werden die Ergebnisse sein. Wenn der Test fehlschlägt, wird auch die Fortschrittsanzeige entfernt.

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

Wenn Sie einen Blick auf die Implementierung des Netzwerktests werfen wollen, sehen Sie sich die Datei, in der ich die Logik vor dem Aufruf behandle.

Die Ergebnisse des Pre-Call-Tests liefern auch eine empfohlene Auflösung und eine MOS-Wert von 0 bis 4,5.

Da dies ein wenig subjektiv ist, werden wir die Möglichkeit hinzufügen, zu entscheiden, ob wir die bevorzugte Auflösung und eine auf der MOS-Bewertung basierende Ergebnisbezeichnung anzeigen wollen, d. h. (gut, schlecht, ausgezeichnet...).

Sie können entscheiden, ob die empfohlene Auflösung und die Punktzahlbezeichnung angezeigt werden sollen, indem Sie die Variable addFeedback Variable unter /src/variables.js. Sie können auch die ErrorNames aus dem npm-Modul nutzen, um je nach Fehler eigene Fehler hinzuzufügen und den Nutzern Empfehlungen zu geben.

Wie geht es weiter?

Das fertige Projekt ist verfügbar auf GitHubverfügbar, und Sie können mehr über die Vonage Video API in unsere Dokumentation.

Teilen Sie:

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.