https://d226lax1qjow5r.cloudfront.net/blog/blogposts/broadcast-video-chat-with-javascript-and-vonage-dr/Blog_Stream-Video_1200x600.png

JavascriptとVonageによるビデオチャットのブロードキャスト

最終更新日 May 5, 2021

所要時間:1 分

このチュートリアルシリーズでは Vonage Video API (旧TokBox OpenTok)を紹介します。Video API は非常に堅牢でカスタマイズ性が高く、各記事では API を使って特定の機能を実装する方法を紹介します。今回は、あなたの Video チャットをオンラインで多くの視聴者にブロードキャストする方法を紹介します。

このアプリケーションにはサーバーサイドのコードが必要なので、ここでは Glitchを使います。また、このGlitchプロジェクトからコードをダウンロードし、お好みのサーバーやホスティング・プラットフォームにデプロイすることもできます(プラットフォームの要件に応じて、いくつかの設定を調整する必要があるかもしれません)。

このシリーズでは、Video API 自体に焦点を当てるため、フロントエンドのフレームワークは使用せず、バニラ Javascript だけを使用します。このチュートリアルの最後には、HTTP ライブストリーミング(HLS)または RTMP ストリームを使用して、大勢の視聴者にビデオチャットをライブ放送できるようになっているはずです。

Screenshot of broadcast page

このアプリケーションの最終的なコードは、この GitHub リポジトリまたは Glitchでリミックス.

前提条件

始める前に、Vonage Video APIアカウントが必要です。 こちら.また Node.jsがインストールされている必要があります(Glitchを使用していない場合)。

このチュートリアルは、シリーズの最初の紹介記事を基に構成されています: 基本的なビデオチャットの構築.Video API を初めて使用する場合は、以下の基本的なセットアップをカバーしているので、この記事を参照することを強くお勧めします:

  • Video APIプロジェクトの作成

  • グリッチでのセットアップ

  • プロジェクトの基本構造

  • セッションの初期化

  • セッションへの接続、購読、公開

  • ビデオチャットの基本的なレイアウトスタイル

Vonage Video API によるブロードキャスト

このプラットフォームは2種類の放送をサポートしている、 双方向ビデオライブ放送ライブストリーミング放送.どちらのタイプのブロードキャストでも、ルーティングされたセッション(Vonage Video API Media Routerを使用するセッション)を使用する必要があります。 VonageビデオAPIメディアルータ).このテーマについては、次のセクションで詳しく説明します。

ライブ・インタラクティブ・ビデオ放送は、多数のクライアントがリアルタイムで互いのオーディオ・ビデオ・ストリームをパブリッシュし、サブスクライブすることを可能にします。ルーティングされたセッションは、クライアント間で最大3,000ストリームのライブ・インタラクティブ・ビデオ放送をサポートできます。

ライブストリーミング放送共有できる HTTPライブストリーミング(HLS)ストリームまたは RTMPストリームを多数の視聴者と共有できます。HLS または RTMP ストリームは、セッションに公開された個々のストリームで構成される単一の Video です。このチュートリアルでは、このタイプのブロードキャストを使用します。

HTTPライブストリーミング(HLS)はメディア・ストリーミング・プロトコルで、インターネット上で連続した長時間のビデオを確実に配信することを目的としている。アップルによって開発され、2009年にリリースされた。

HLS は、配信に CDN を使用し、高遅延(15-20 秒)でインタラクションのない伝統的な放送です。HLSの視聴者は15-20秒のレイテンシでコンテンツを受信するので、インタラクティブなユースケースには直接適さない。

リアルタイム・メッセージング・プロトコル(RTMP)は、オーディオ、ビデオ、データの伝送用に設計されたTCPベースのプロトコルである。元々はMacromedia社が独自に開発したプロトコルであったが、現在はAdobe社が公開したオープンな仕様となっている。

RTMP は HLS よりも低レイテンシ(約 5 秒)ですが、インタラクティブ性を必要とするユースケースには向いていません。Vonage Video API で作成されたコンテンツを Facebook や YouTube Live などのソーシャル・メディア・ビデオ・プラットフォームにプッシュするために RTMP を使用します。

初期設定

基本的なビデオチャットの上に構築しているので、前のチュートリアルで構築した基本的なビデオチャットのプロジェクトをリミックスすることから始めます。下の大きなRemixボタンをクリックしてください。👇

remix this

フォルダー構造は次のようになっているはずだ:

Folder structure of the project

冒頭で述べたように、TokBox OpenTokはVonage Video APIになりました。パッケージ名に変更はありませんので、コードの中でOpenTokを参照することに変わりはありません。

グリッチ・プロジェクトをリミックスした場合、あなたの server.jsファイルはすでにこのようになっているはずだ:

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

ビデオチャットを開始するには .envファイルにアクセスし、プロジェクトのAPIキーとシークレットを入力してください。これが完了したら、テキストチャットを動作させるためにクライアント側のコードに取り組みます。 server.jsファイルをもう一度見てみましょう。

必要なマークアップを追加する

ユーザーがセッションを作成したり参加したりするためのランディングページ、ビデオチャットの参加者のためのビデオチャットページ、ブロードキャストストリームを表示するページです。

放送用の追加ページを作成する必要がある。そのために broadcast.htmlファイルを viewsフォルダに 新規ファイルボタンをクリックして、ファイルをフォルダに追加しよう。ファイル名を views/broadcast.htmlと名付け、以下のマークアップをページに貼り付けます。

Add a broadcast.html to the views folder

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Broadcast Video Chat</title>
    <meta
      name="description"
      content="Broadcast your video chat to a large audience 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>Video broadcast</h1>
    </header>

    <main>
      <video id="video" class="broadcast-video"></video>
    </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://cdn.jsdelivr.net/npm/hls.js@latest"></script>
    <script src="/broadcast.js"></script>
  </body>
</html>

ここで行われていることはそれほど多くはない。 video要素で、放送開始時に HLS ストリームを収容する。

また、放送に関連したマークアップも追加する。 index.htmlページには、放送を開始したり停止したりするためのボタンや、共有可能なHLSリンクを生成するためのボタンのような、放送関連のマークアップも追加します。

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

  <!-- Add the broadcast controls -->
  <div class="broadcast">
    <button id="startBroadcast">Start Broadcast</button>
    <button id="stopBroadcast" class="hidden">Stop Broadcast</button>
  </div>
</main>

放送コントロールのスタイル

次に、新しく追加されたマークアップのためのスタイルをいくつか入れてみよう。ここではあまり派手なことはせず、いくつかの位置と、放送の開始/停止を始めるときに説明するボタンの状態を設定するだけだ。

/* To position the controls in the bottom-right corner */
.broadcast {
  position: absolute;
  right: 0;
  bottom: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
}

.broadcast a,
.broadcast button {
  margin-bottom: 1em;
}

/* This is to centre the broadcast video */
.broadcast-video {
  margin: auto;
}

さて、セッションを開始すると、インターフェイスは次のようになるはずだ:

Styling the broadcast controls to the bottom-right corner of the screen

これは最終的なスタイリングではないが、アプリケーションのブロードキャスト機能を作り上げる段階ではこれで十分だろう。

放送の開始/停止

Video API を使ったブロードキャストの鍵は startBroadcast()メソッドと stopBroadcast()メソッドです。これらのメソッドは server.jsファイルから呼び出されます。メソッドは3つのパラメータを受け取ります。 startBroadcast()メソッドは3つのパラメータを受け取ります。セッションのセッションID、ブロードキャストのオプション、そしてコールバック関数です。セッションIDはクライアントサイドから POSTリクエストを通してクライアントサイドからセッションIDを取得します。そのためのルートを設定しましょう。

// Required to read the body of a POST request
app.use(express.json());

// Declare an object to store the broadcast information returned by the SDK
let broadcastData = {};

app.post("/broadcast/start", (request, response) => {
  const sessionId = request.body.sessionId;

  const broadcastOptions = {
    outputs: {
      hls: {},
    },
  };

  OT.startBroadcast(sessionId, broadcastOptions, (error, broadcast) => {
    if (error) {
      console.log(error);
      response.status(503);
      response.send({ error });
    }
    // Assign the response from the SDK to the broadcastData object
    broadcastData = broadcast;
    response.status(200);
    response.send({ broadcast: broadcast });
  });
});

解像度やレイアウトなど、ブロードキャスト・オプションとして含めることのできるオプション・プロパティは他にもあるが、今はデフォルトを使うことにする。詳しくは APIリファレンスを参照してください。

ブロードキャストを止めるルートも設定しておこう。この stopBroadcast()メソッドにはブロードキャストIDが必要です。

app.post("/broadcast/stop", (request, response) => {
  const broadcastId = request.body.broadcastId;
  OT.stopBroadcast(broadcastId, (error, broadcast) => {
    if (error) console.log(error);
    response.status(200);
    response.send({
      status: broadcast.status
    });
  });
});

この新機能に対応するために client.jsファイルを少し修正する必要がある。ファイルの client.jsファイルに sessionをグローバル変数にする。

+ let session;
function initializeSession(apiKey, sessionId, token) {
-  const session = OT.initSession(apiKey, sessionId);
+ session = OT.initSession(apiKey, sessionId);
  // more code below
}

また、セッションのメディアモードを ルーティングに変更しなければならない。

以前はね:

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) => {
+    // Set the media mode to routed here
+    OT.createSession({ mediaMode: "routed" }, (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);
      }
    });
  }
});

また、ブロードキャストに関する情報を保持するために broadcast変数を宣言して、ブロードキャストに関する情報を保持する必要がある。とりあえず、期待通りに動作していることを検証するために、レスポンスをコンソールに記録してみよう。

let broadcast;

const startBroadcastBtn = document.getElementById("startBroadcast");
startBroadcastBtn.addEventListener("click", startBroadCast, false);

function startBroadCast() {
  fetch("/broadcast/start", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ sessionId: session.sessionId })
  })
    .then(res => {
      return res.json();
    })
    .then(res => {
      broadcast = res.broadcast;
      console.log(res);
    })
    .catch(handleCallback);
}

const stopBroadcastBtn = document.getElementById("stopBroadcast");
stopBroadcastBtn.addEventListener("click", stopBroadCast, false);

function stopBroadCast() {
  fetch("/broadcast/stop", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ broadcastId: broadcast.id })
  })
    .then(res => {
      return res.json();
    })
    .then(res => {
      console.log(res);
    })
    .catch(handleCallback);
}

放送を開始したり停止したりするときにコンソールを開くと、次のように表示されるはずだ:

Console log messages when broadcast starts and stops

理論的には、ビデオチャットをサポートするプレーヤーにストリーミングするためのHLSリンクにアクセスできるようになったので、ここで止めることもできます。既に HLS ストリームを扱うものをお持ちの場合は、ご自由にお使いください。このチュートリアルの残りの部分では、ブロードキャスト・ストリームがどのようなものかを見ることができるように、基本的な実装について説明します。

ハンドルボタンの状態

しかしその前に、ボタンの状態に関するスタイリングをいくつか追加しよう。お気づきのように、ボタンを押してから ブロードキャスト開始ボタンを押してからコンソールにログが記録されるまでにタイムラグがあります。ユーザーエクスペリエンスを向上させるために、リクエストがサーバーに送信されたことをユーザーに示す表示を提供したいと思います。

流れはこんな感じだ:

Button state flow

開始ボタンと停止ボタンの両方を表示する代わりに、一度に関連するボタンを1つだけ表示します。また、一度ボタンがクリックされると、処理中に何度もクリックされないようにする。非表示と無効の状態に対処するために、CSSクラスをいくつか追加しましょう。

/* These are for the button states */
.hidden {
  display: none;
}

.disabled {
  cursor: not-allowed;
  opacity: 0.5;
  pointer-events: none;
}

startとstopが同じフローを持つことを考えると、状態変更に必要なCSSクラスは、両方のボタンで同じになり、交互に適用されるだけである。このような変更は、文字列 "start "または "stop "を受け取り、適切なボタンをターゲットとする関数に抽象化することができる。

// Button state while awaiting response from server
function pendingBtnState(statusString) {
  const btn = document.getElementById(statusString + "Broadcast");
  btn.classList.add("disabled");
  btn.setAttribute("data-original", btn.textContent);
  btn.textContent = "Processing…";
}

// Switch button state once server responds
function activeBtnState(statusString) {
  const activeBtn =
    statusString === "start"
      ? document.getElementById("startBroadcast")
      : document.getElementById("stopBroadcast");
  const inactiveBtn =
    statusString === "stop"
      ? document.getElementById("startBroadcast")
      : document.getElementById("stopBroadcast");

  inactiveBtn.classList.remove("disabled");
  inactiveBtn.textContent = inactiveBtn.getAttribute("data-original");
  inactiveBtn.removeAttribute("data-original");
  inactiveBtn.classList.add("hidden");
  activeBtn.classList.remove("hidden");
}

これらの関数を、ブロードキャストを開始したり停止したりするためのフェッチリクエストに組み込んでみよう。

function startBroadCast() {
  // To put the Start button into the pending state
  pendingBtnState("start");

  fetch("/broadcast/start", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ sessionId: session.sessionId })
  })
    .then(res => {
      return res.json();
    })
    .then(res => {
      broadcast = res.broadcast;
      // To hide the Start button and show the Stop button
      activeBtnState("stop");
    })
    .catch(handleCallback);
}

function stopBroadCast() {
  // To put the Stop button into the pending state
  pendingBtnState("stop");

  fetch("/broadcast/stop", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ broadcastId: broadcast.id })
  })
    .then(res => {
      return res.json();
    })
    .then(res => {
      // To hide the Stop button and show the Start button
      activeBtnState("start");
    })
    .catch(handleCallback);
}

共有可能なHLSリンクの作成

SDK から返される Broadcast オブジェクトには、HLS をサポートするビデオプレーヤーで利用できる HLS ブロードキャスト URL が含まれています。これを利用して、Broadcast ページへのリンクを作成してみましょう。最初に broadcast.htmlファイルを作成したので、そのページにブロードキャストをパイプで送信しましょう。そのために server.jsファイルにルートを設定しよう。

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

ブロードキャストされるセッションの存在をチェックする別のルートを追加します。存在する場合、成功レスポンスはブロードキャストのURLとそのステータスを渡します。

app.get("/broadcast/hls/:room", (request, response) => {
  const roomName = request.params.room;
  if (sessions[roomName]) {
    response.status(200);
    response.send({
      hls: broadcastData.broadcastUrls.hls,
      status: broadcastData.status
    });
  } else {
    response.status(204);
  }
});

私たちの index.htmlページで、ブロードキャスト・コントロールに以下を追加する。 div:

<div class="broadcast">
  <!-- Add link to the Broadcast page and a means to copy to clipboard -->
  <a class="hidden" id="hlsLink" target="_blank" rel="noopener noreferrer"
    >Open Broadcast page</a
  >
  <p class="invisible" id="hlsCopyTarget"></p>
  <button class="hidden" id="copyLink">Copy HLS link</button>

  <button id="startBroadcast">Start Broadcast</button>
  <button id="stopBroadcast" class="hidden">Stop Broadcast</button>
</div>

そして、いくつかの追加CSS styles.css:

.invisible {
  position: absolute;
  opacity: 0;
  z-index: -1;
}

これらの変更の結果、ブロードキャスト・ページを別のタブまたはウィンドウで開くリンクが表示されます。ボタンをクリックすると、そのリンクがブロードキャストページにコピーされ、ブロードキャストを共有することができます。ブロードキャストページのリンクを作成するために、ブロードキャストレスポンスから HLS URL とルーム名(URL から)を取得する必要があります。

const url = new URL(window.location.href);
const roomName = url.pathname.split("/")[2];

// To show/hide the HLS links when the broadcast starts/stops
function hlsLinkState(statusString) {
  if (statusString === "start") {
    document.getElementById("hlsLink").classList.remove("hidden");
    document.getElementById("copyLink").classList.remove("hidden");
  } else {
    document.getElementById("hlsLink").classList.add("hidden");
    document.getElementById("copyLink").classList.add("hidden");
  }
}

// Create the link to the broadcast page
function composeHlsLink(link) {
  hlsLinkState("start");
  const hlsLinkUrl =
    "https://" + location.host + "/broadcast/" + roomName + "?hls=" + link;
  const hlsLink = document.getElementById("hlsLink");
  const hlsCopyTarget = document.getElementById("hlsCopyTarget");
  hlsLink.href = hlsLinkUrl;
  hlsCopyTarget.innerHTML = hlsLinkUrl;
}

ブロードキャストを開始/停止するためのフェッチリクエストにも、これらの新しい関数を追加しよう:

function startBroadCast() {
  pendingBtnState("start");

  fetch("/broadcast/start", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ sessionId: session.sessionId })
  })
    .then(res => {
      return res.json();
    })
    .then(res => {
      broadcast = res.broadcast;
      activeBtnState("stop");
      // Compose the link to the broadcast page
      composeHlsLink(res.broadcast.broadcastUrls.hls);
    })
    .catch(handleCallback);
}

function stopBroadCast() {
  pendingBtnState("stop");

  fetch("/broadcast/stop", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ broadcastId: broadcast.id })
  })
    .then(res => {
      return res.json();
    })
    .then(res => {
      activeBtnState("start");
      // Hide the links when the broadcast has stopped
      hlsLinkState("stop");
    })
    .catch(handleCallback);
}

HLSストリームを扱うことができるビデオプレーヤーは数多くあり、プレーヤーのインターフェイスをさまざまなレベルでカスタマイズすることができますが、基本的なことを説明するために、このチュートリアルでは、以下のビデオプレーヤーをロードします。 hls.jsをロードしてストリームを再生します。ストリームを再生するために broadcast.jsファイルを publicフォルダにファイルを作成します。

const url = new URL(window.location.href);
const roomName = url.pathname.split("/")[2];
const hlsLink = url.searchParams.get("hls");

fetch("/broadcast/hls/" + roomName)
  .then(res => {
    return res.json();
  })
  .then(res => {
    playStream(hlsLink);
  })
  .catch(error => console.error(error));

// Refer to hls.js documentation for more options
function playStream(hlsLink) {
  const video = document.getElementById("video");
  const videoSrc = hlsLink;

  if (Hls.isSupported()) {
    const hls = new Hls();
    hls.loadSource(videoSrc);
    hls.attachMedia(video);
    hls.on(Hls.Events.MANIFEST_PARSED, function() {
      video.play();
    });
  } else if (video.canPlayType("application/vnd.apple.mpegurl")) {
    video.src = videoSrc;
    video.addEventListener("loadedmetadata", function() {
      video.play();
    });
  }
}

このチュートリアルの最後は、クリップボードへのコピーのネイティブ実装だ。自由に clipboard.jsのようなライブラリを自由に使ってください。

const copyLinkBtn = document.getElementById("copyLink");
copyLinkBtn.addEventListener("click", copyHlsLink, false);

function copyHlsLink() {
  const hlsCopyTarget = document.getElementById("hlsCopyTarget");
  const range = document.createRange();
  range.selectNode(hlsCopyTarget);
  window.getSelection().addRange(range);

  try {
    const successful = document.execCommand("copy");
    const msg = successful ? "successful" : "unsuccessful";
    console.log("Copy command was " + msg);
  } catch (err) {
    console.log("Oops, unable to copy");
  }
  window.getSelection().removeAllRanges();
}

ビデオチャットのページとブロードキャストのページには、それぞれこのようなものが表示されます:

Video chat page

Broadcast page

次はどうする?

最終コード グリッチGitHubにある最終的なコードには、この長い投稿で説明したことがすべて含まれているが、再整理されているため、コードはよりクリーンで保守しやすくなっている。コードをリミックスしたりクローンしたりして、自由に遊んでみてほしい。

Vonage Video APIで構築できる機能は他にもあり、今後のチュートリアルで紹介する予定です。 総合ドキュメントサイト.何か問題が発生したり、質問がある場合は、私たちの コミュニティ・スラック.お読みいただきありがとうございました!

シェア:

https://a.storyblok.com/f/270183/384x384/46621147f0/huijing.png
Hui Jing Chenヴォネージの卒業生

ホイ・ジンはNexmoのデベロッパー・アドボケイト。CSSとタイポグラフィをこよなく愛する。