https://d226lax1qjow5r.cloudfront.net/blog/blogposts/stream-a-video-chat-with-vonage-video-api-dr/Blog_Stream-Video_1200x600.png

Streamen Sie einen Video-Chat mit Vonage Video API

Zuletzt aktualisiert am May 5, 2021

Lesedauer: 5 Minuten

In dieser Reihe von Tutorials werden wir die Vonage Video API (ehemals TokBox OpenTok) und was Sie damit bauen können. Die Video API ist sehr robust und in hohem Maße anpassbar, und in jedem Beitrag zeigen wir, wie man eine bestimmte Funktion mit der API implementiert. Dieses Mal sehen wir uns an, wie Sie Ihren Video-Chat an ein Publikum streamen können, das nicht am Chat teilnimmt.

Da diese Anwendung etwas serverseitigen Code erfordert, werden wir Glitch um die Einrichtung zu erleichtern. Sie können den Code auch von diesem Glitch-Projekt herunterladen und auf dem Server oder der Hosting-Plattform Ihrer Wahl bereitstellen (je nach den Anforderungen Ihrer Plattform müssen Sie möglicherweise einige Konfigurationsänderungen vornehmen).

Wir werden in dieser Serie keine Front-End-Frameworks verwenden, sondern nur Vanilla Javascript, um den Fokus auf die Video API selbst zu legen. Am Ende dieses Tutorials sollte Ihre Video-Chat-Anwendung auch die Möglichkeit bieten, den Video-Chat-Stream einfach anzusehen.

Screenshot of viewer page

Der endgültige Code für diese Anwendung finden Sie in diesem GitHub-Repository oder neu gemischt auf Glitch.

Voraussetzungen

Bevor wir loslegen, benötigen Sie einen Vonage Video API Account, den Sie kostenlos erstellen können hier. Sie benötigen außerdem Node.js installiert haben (wenn Sie nicht Glitch verwenden).

Dieses Tutorial baut auf dem ersten einführenden Beitrag der Serie auf: Aufbau eines einfachen Video-Chats. Wenn Sie die Video API zum ersten Mal verwenden, empfehlen wir Ihnen dringend, diesen Beitrag durchzuarbeiten, da er die folgenden grundlegenden Einstellungen abdeckt:

  • Erstellen eines Vonage Video API-Projekts

  • Einrichten auf Glitch

  • Grundlegende Projektstruktur

  • Initialisierung einer Sitzung

  • Verbinden mit der Sitzung, Abonnieren und Veröffentlichen

  • Grundlegende Layout-Stile für einen Video-Chat

Token-Erstellung und Rollen

Jeder Benutzer, der eine Verbindung zu einer Sitzung herstellt, muss mit einem Token authentifiziert werden. Jedem Token wird eine Rolle zugewiesen, die bestimmt, was der Client tun kann, wenn er verbunden ist. Es gibt drei verfügbare Rollen, Teilnehmer, Herausgeber und Moderator. In diesem Lehrgang werden wir nur die ersten beiden verwenden.

Ein Verleger kann eine Verbindung zu Sitzungen herstellen, Audio- und Videostreams für die Sitzung veröffentlichen und die Sitzungen anderer Clients abonnieren. Ein Abonnent kann eine Verbindung zu Sitzungen herstellen und die Sitzungen anderer Clients abonnieren, aber kann nicht veröffentlichen in der Sitzung veröffentlichen.

Crude illustration of subscribers and publishers

In diesem Tutorial erhalten die Teilnehmer Verleger-Token, während die Zuschauer Abonnenten-Token erhalten.

Weitere Informationen über Token finden Sie in der Dokumentation.

Erstmalige Einrichtung

Da wir auf einem grundlegenden Video-Chat aufbauen, beginnen Sie damit, das Projekt für den grundlegenden Video-Chat aus dem vorherigen Tutorial zu remixen. Klicken Sie dazu auf die große Schaltfläche Remix unten. 👇

remix this

Ihre Ordnerstruktur sollte in etwa so aussehen:

Folder structure of the project

Wie eingangs erwähnt, ist TokBox OpenTok jetzt Vonage Video API. Wir haben keine Änderungen an unseren Paketnamen vorgenommen, so dass Sie in Ihrem Code weiterhin auf OpenTok verweisen werden.

Wenn Sie das Glitch-Projekt neu gemischt haben, sollte Ihre server.js Datei bereits wie folgt aussehen:

const express = require("express");
const app = express();
const OpenTok = require("opentok");
const OT = new OpenTok(process.env.API_KEY, process.env.API_SECRET);

let sessions = {};

app.use(express.static("public"));

app.get("/", (request, response) => {
  response.sendFile(__dirname + "/views/landing.html");
});

app.get("/session/:room", (request, response) => {
  response.sendFile(__dirname + "/views/index.html");
});

app.post("/session/:room", (request, response) => {
  const roomName = request.params.room;
  // Check if the session already exists
  if (sessions[roomName]) {
    // Generate the token
    generateToken(roomName, response);
  } else {
    // If the session does not exist, create one
    OT.createSession((error, session) => {
      if (error) {
        console.log("Error creating session:", error);
      } else {
        // Store the session in the sessions object
        sessions[roomName] = session.sessionId;
        // Generate the token
        generateToken(roomName, response);
      }
    });
  }
});

function generateToken(roomName, response) {
  // Configure token options
  const tokenOptions = {
    role: "publisher",
    data: `roomname=${roomName}`
  };
  // Generate token with the Video API Client SDK
  let token = OT.generateToken(
    sessions[roomName],
    tokenOptions
  );
  // Send the required credentials back to to the client
  // as a response from the fetch request
  response.status(200);
  response.send({
    sessionId: sessions[roomName],
    token: token,
    apiKey: process.env.API_KEY
  });
}

const listener = app.listen(process.env.PORT, () => {
  console.log("Your app is listening on port " + listener.address().port);
});

Um den Video-Chat zum Laufen zu bringen, gehen Sie zur .env und geben Sie den API-Schlüssel und das Geheimnis für Ihr Projekt ein, die Sie im Dashboard finden können. Sobald das erledigt ist, werden wir einige Ergänzungen am Projekt vornehmen, um eine Schnittstelle für die Betrachter bereitzustellen.

Fügen Sie das erforderliche Markup hinzu

Unsere Anwendung wird aus drei Seiten bestehen: einer Landing Page, auf der die Nutzer eine Sitzung erstellen oder ihr beitreten können und auswählen können, ob sie Zuschauer oder Teilnehmer sein wollen, und den beiden Video-Chat-Seiten für die jeweilige Rolle.

Wir müssen eine zusätzliche Seite für den Betrachter erstellen. Fügen wir eine viewer.html Datei in den Ordner views Ordner eine Datei hinzu, indem wir auf die Schaltfläche Neue Datei in der linken Seitenleiste klicken. Benennen Sie die Datei views/viewer.html und fügen Sie das folgende Markup in die Seite ein. Diese Seite ist fast genau die gleiche wie die index.html Datei, außer dass sie kein div für den Herausgeber.

Add a viewer.html to the views folder

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Stream your video chat</title>
    <meta
      name="description"
      content="Stream a basic audio-video chat with Vonage Video API in Node.js"
    />
    <link
      id="favicon"
      rel="icon"
      href="https://tokbox.com/developer/favicon.ico"
      type="image/x-icon"
    />
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />

    <link rel="stylesheet" href="/style.css" />
  </head>

  <body>
    <header>
      <h1>Viewer</h1>
    </header>

    <main>
      <div id="subscriber" class="subscriber"></div>
    </main>

    <footer>
      <p>
        <small
          >Built on <a href="https://glitch.com">Glitch</a> with the
          <a href="https://tokbox.com/developer/">Vonage Video API</a>.</small
        >
      </p>
    </footer>

    <script src="https://static.opentok.com/v2/js/opentok.min.js"></script>
    <script src="/viewer.js"></script>
  </body>
</html>

Die viewer.html und die index.html werden unterschiedliche Skriptdateien verwenden, da ihre Implementierung aufgrund ihrer jeweiligen Token-Rollen, wie im obigen Abschnitt erläutert, leicht unterschiedlich ist.

Als Nächstes werden wir einige Änderungen am Formular auf der landing.html um eine Option einzubauen, mit der die Benutzer ihre Rollen über Optionsfelder auswählen können. Wenn sie wählen Betrachterauswählen, werden sie auf die Seite weitergeleitet, die ihnen einen Stream des Videochats zeigt. Wenn sie wählen Teilnehmerwählen, erscheint eine weitere Texteingabe für den Benutzernamen, der zur Identifizierung ihres Streams verwendet wird.

<form id="registration" class="registration">
  <label>
    <span>Room</span>
    <input
      type="text"
      name="room-name"
      placeholder="Enter room name"
      required
    />
  </label>
  
  <!-- Add the user type radio buttons -->
  <p>Select your role:</p>
  <fieldset id="userRoles">
    <label>
      <input type="radio" name="user-type" value="viewer" checked />
      <span>Viewer</span>
    </label>

    <label>
      <input type="radio" name="user-type" value="participant" />
      <span>Participant</span>
    </label>
  </fieldset>
  
  <!-- Add the user name input field and label -->
  <label id="userName" class="hidden">
    <span>User name</span>
    <input type="text" name="user-name" placeholder="Enter your name" />
  </label>

  <button>Enter</button>
</form>

Gestalten Sie das Landing Page-Formular

Ergänzen wir die vorhandenen Stile, um das neue Feldset und die Optionsfelder zu berücksichtigen.

fieldset {
  border: 0;
  display: flex;
  justify-content: space-between;
  margin-bottom: 1em;
}

fieldset label {
  padding: 0.25em 0em;
  cursor: pointer;
}

.hidden {
  display: none;
}

Basic styles for fieldset and radio buttons

Refaktorieren Sie das Client-seitige Javascript

Arbeiten wir zuerst an der landing.html Seite vor. Für das bedingte Ein- und Ausblenden des Feldes für den Benutzernamen können wir einen Ereignis-Listener hinzufügen, der den Wert des ausgewählten Optionsfeldes überprüft und die Stile entsprechend umschaltet.

const userRoles = document.getElementById("userRoles");
const userName = document.getElementById("userName");
const userNameField = document.querySelector('[name="user-name"]');
userRoles.addEventListener(
  "click",
  event => {
    if (event.target.value === "participant") {
      userName.classList.remove("hidden");
      userNameField.required = true;
    } else {
      userName.classList.add("hidden");
      userNameField.required = false;
    }
  },
  false
);

Wir müssen auch die Logik ändern, um unsere Benutzer auf die richtigen Seiten zu schicken, je nachdem, ob sie die Option Betrachter oder Teilnehmer. Betrachter werden zu /session/viewer/ROOM_NAME gesendet, während die Teilnehmer an /session/participant/ROOM_NAME?username=USER_NAME. Wir verwenden den Query-String in der URL, um den Benutzernamen an den Server zu übergeben.

const form = document.getElementById("registration");
form.addEventListener("submit", event => {
  event.preventDefault();
  
  // Check the selected option and redirect accordingly
  const isViewer = form.elements["user-type"].value === "viewer";

  if (isViewer) {
    location.href = `/session/viewer/${form.elements["room-name"].value}`;
  } else {
    location.href = `/session/participant/${form.elements["room-name"].value}?username=${form.elements["user-name"].value}`;
  }
});

Als nächstes erstellen wir die viewer.js Datei für die viewer.html Seite. Ähnlich wie bei der viewer.htmlgemacht haben, klicken Sie auf Neue Datei an, aber diesmal fügen Sie die Javascript-Dateien in den Ordner public Ordner hinzu.

Ihr Projektordner sollte nun wie folgt aussehen:

Project structure after all files are added

Die Datei viewer.js Datei ist etwas kürzer als die client.js Datei, da sie die Erstellung eines Verlags nicht enthält. Wir stellen eine POST Anfrage an /session/viewer/ROOM_NAME und erhalten die notwendigen Antwortdaten, um eine Verbindung zu einer Sitzung herzustellen.

fetch(location.pathname, { method: "POST" })
  .then(res => {
    return res.json();
  })
  .then(res => {
    const apiKey = res.apiKey;
    const sessionId = res.sessionId;
    const token = res.token;
    initializeSession(apiKey, sessionId, token);
  })
  .catch(handleCallback);

function initializeSession(apiKey, sessionId, token) {
  // Create a session object with the sessionId
  const session = OT.initSession(apiKey, sessionId);

  // Connect to the session
  session.connect(token, error => handleCallback(error));

  // Subscribe to a newly created stream
  session.on("streamCreated", event => {
    session.subscribe(
      event.stream,
      "subscriber",
      {
        insertMode: "append",
        width: "100%",
        height: "100%",
        name: event.stream.name
      },
      handleCallback
    );
  });
}

// Callback handler
function handleCallback(error) {
  if (error) {
    console.log("error: " + error.message);
  } else {
    console.log("callback success");
  }
}

Wir müssen auch einige kleinere Anpassungen an der client.js Datei vornehmen, da wir den Stream für jeden Teilnehmer mit dem Benutzernamen versehen wollen, den er auf der Landing Page eingegeben hat.

fetch(location.pathname, { method: "POST" })
  .then(res => {
    return res.json();
  })
  .then(res => {
    const apiKey = res.apiKey;
    const sessionId = res.sessionId;
    const token = res.token;
    // Declare the stream name and pass it to the initializeSession() function
    const streamName = res.streamName;
    initializeSession(apiKey, sessionId, token, streamName);
  })
  .catch(handleCallback);

Die Funktion initializeSession() Funktion nimmt nun einen weiteren Parameter für streamName und wird in der initPublisher() Methode und der subscribe() Methode. Beide Methoden akzeptieren ein optionales Eigenschaftsargument, mit dem wir Anpassungsoptionen für die Streams übergeben können.

// Create a publisher
const publisher = OT.initPublisher(
  "publisher",
  {
    insertMode: "append",
    width: "100%",
    height: "100%",
    name: streamName
  },
  handleCallback
);

// Subscribe to a newly created stream
session.on("streamCreated", event => {
  session.subscribe(
    event.stream,
    "subscriber",
    {
      insertMode: "append",
      width: "100%",
      height: "100%",
      name: event.stream.name
    },
    handleCallback
  );
});

Ihre endgültigen client.js Dateien werden wie folgt aussehen:

fetch(location.pathname, { method: "POST" })
  .then(res => {
    return res.json();
  })
  .then(res => {
    const apiKey = res.apiKey;
    const sessionId = res.sessionId;
    const token = res.token;
    const streamName = res.streamName;
    initializeSession(apiKey, sessionId, token, streamName);
  })
  .catch(handleCallback);

function initializeSession(apiKey, sessionId, token, streamName) {
  // Create a session object with the sessionId
  const session = OT.initSession(apiKey, sessionId);

  // Create a publisher
  const publisher = OT.initPublisher(
    "publisher",
    {
      insertMode: "append",
      width: "100%",
      height: "100%",
      name: streamName
    },
    handleCallback
  );

  // Connect to the session
  session.connect(token, error => {
    // If the connection is successful, initialize the publisher and publish to the session
    if (error) {
      handleCallback(error);
    } else {
      session.publish(publisher, handleCallback);
    }
  });

  // Subscribe to a newly created stream
  session.on("streamCreated", event => {
    session.subscribe(
      event.stream,
      "subscriber",
      {
        insertMode: "append",
        width: "100%",
        height: "100%",
        name: event.stream.name
      },
      handleCallback
    );
  });
}

// Callback handler
function handleCallback(error) {
  if (error) {
    console.log("error: " + error.message);
  } else {
    console.log("callback success");
  }
}

Routes auf der Server-Seite handhaben

Der letzte Teil, bevor alles zusammenkommt, ist die server.js Datei, in der die Routen definiert sind. Wir müssen die Routen verarbeiten, um den Betrachter Seite (viewer.html) als auch die Teilnehmer Seite (index.html).

app.get("/session/participant/:room", (request, response) => {
  response.sendFile(__dirname + "/views/index.html");
});

app.get("/session/viewer/:room", (request, response) => {
  response.sendFile(__dirname + "/views/viewer.html");
});

Anstelle der neu gemischten generateToken() Funktion werden wir zwei verschiedene Funktionen für die beiden Rollen verwenden.

function generatePublisherToken(roomName, streamName, response) {
  // Configure token options
  const tokenOptions = {
    role: "publisher",
    data: `roomname=${roomName}?streamname=${streamName}`
  };
  // Generate token with the OpenTok SDK
  let token = OT.generateToken(
    sessions[roomName],
    tokenOptions
  );
  // Send the required credentials back to to the client
  // as a response from the fetch request
  response.status(200);
  response.send({
    sessionId: sessions[roomName],
    token: token,
    apiKey: process.env.API_KEY,
    streamName: streamName
  });
}

function generateSubscriberToken(roomName, response) {
  // Configure token options
  const tokenOptions = {
    role: "subscriber",
    data: `roomname=${roomName}`
  };
  // Generate token with the OpenTok SDK
  let token = OT.generateToken(
    sessions[roomName],
    tokenOptions
  );
  // Send the required credentials back to to the client
  // as a response from the fetch request
  response.status(200);
  response.send({
    sessionId: sessions[roomName],
    token: token,
    apiKey: process.env.API_KEY
  });
}

Für Betrachter wird der Raumname nach dem Laden der Betrachterseite über eine Anfrage an den Server gesendet. POST Anfrage. Dies wird über die folgende Route abgewickelt:

app.post("/session/viewer/:room", (request, response) => {
  const roomName = request.params.room;
  // Check if the session already exists
  if (sessions[roomName]) {
    // Generate the token
    generateSubscriberToken(roomName, response);
  } else {
    // If the session does not exist, create one
    OT.createSession((error, session) => {
      if (error) {
        console.log("Error creating session:", error);
      } else {
        // Store the session in the sessions object
        sessions[roomName] = session.sessionId;
        // Generate the token
        generateSubscriberToken(roomName, response);
      }
    });
  }
});

In ähnlicher Weise werden für Teilnehmer, sobald die Teilnehmerseite geladen ist, der Raumname und der Benutzername über eine Anfrage an den Server gesendet. POST Anfrage an den Server gesendet, und die entsprechende Route wird wie folgt behandelt:

// Middleware to read the body of the request
app.use(express.json());

app.post("/session/participant/:room", (request, response) => {
  const roomName = request.params.room;
  const streamName = request.body.username;
  // Check if the session already exists
  if (sessions[roomName]) {
    // Generate the token
    generatePublisherToken(roomName, streamName, response);
  } else {
    // If the session does not exist, create one
    OT.createSession((error, session) => {
      if (error) {
        console.log("Error creating session:", error);
      } else {
        // Store the session in the sessions object
        sessions[roomName] = session.sessionId;
        // Generate the token
        generatePublisherToken(roomName, streamName, response);
      }
    });
  }
});

Und damit können die Zuschauer die Streams der Teilnehmer auf einer einzigen Seite sehen, während die Teilnehmer einen Videochat miteinander führen.

Screenshot of viewer page

Screenshot of participant page

Sehen Sie sich den endgültigen Code auf Glitch oder GitHub und fühlen Sie sich frei, den Code zu remixen oder zu klonen und selbst damit herumzuspielen.

Was kommt als Nächstes?

Es gibt noch weitere Funktionen, die wir mit der Video API von Vonage erstellen können und die in zukünftigen Tutorials behandelt werden, aber in der Zwischenzeit können Sie mehr auf unserer umfassenden Dokumentationsseite. Wenn Sie auf Probleme stoßen oder Fragen haben, wenden Sie sich an uns auf unserem Gemeinschaft Slack. Vielen Dank fürs Lesen!

Teilen Sie:

https://a.storyblok.com/f/270183/384x384/46621147f0/huijing.png
Hui Jing ChenVonage Ehemalige

Hui Jing ist Developer Advocate bei Nexmo. Sie hat eine übermäßige Liebe zu CSS und Typografie und ist allgemein leidenschaftlich über alle Dinge im Web.