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

Vonage Video APIを使用した待合室と通話前のベストプラクティス

最終更新日 July 13, 2021

所要時間:1 分

独自のビデオ会議ソリューションを開発する場合、通話前のエクスペリエンスを向上させることが重要です。ユーザーが使用するオーディオデバイスとビデオデバイスを選択できるようにし、マイクがあなたのスピーチを検出し、ネットワーク強度が十分であることを確認します。これらすべてのボックスをチェックすることで、より堅牢なアプリケーションを構築することができ、アプリケーションのエンドユーザーとの摩擦を減らすことが期待されるいくつかの問題をキャッチすることができます。

最近では、アプリケーションにさまざまな役割を持たせることも一般的になっています。医療、教育、ウェビナーなどの分野で活動する場合は、モデレーターを置くとよいでしょう。このブログ記事では、他の参加者が公開を開始するまで、司会者がセッションに参加するのを待たせる方法について説明します。

まとめると、このサンプル・アプリが実装しているのは

モデレーション。モデレーター/ホストがセッションへの公開を開始するのを待ちます。デバイスの選択。通話前のベストプラクティス。これには、通話前の接続性と品質テスト、およびオーディオ・レベル・インジケータなどの他のベスト・プラクティスが含まれる。

もしこれがプランのように聞こえるなら、その辺に留まっていてほしい。ちょっと怠け者の気分で、そうしたいのであれば 完成したリポジトリはこちら.

プロジェクトの構成

サーバーサイド

サーバーの サーバーのメインファイルは基本的なNode.jsのエクスプレス・サーバーで、選択したルート(/hostまたは/Participant)に応じて2つのHTMLファイルを提供する。このサーバーは、セッションの認証情報(トークンとセッションID)の生成も担当する。

サーバーはホスト用のモデレータートークンか、参加者用のパブリッシャートークンを生成します。トークン生成の詳細については このリンク.サーバーはセッションと roomNamesのマップをメモリに保存します。本番アプリケーションでは、これらのセッションをデータベースなどに保存する必要があります。

クライアント側

このアプリケーションでは、Webpackを使用してすべてのJavaScriptファイルをバンドルし、アプリケーションをよりスケーラブルでわかりやすくしている。また、Bootstrapを使用してUIデザインプロセスを簡素化しています。

すべてのJavaScriptファイルは フォルダ内にあります。:

メインのエントリー・ポイントは srcフォルダ内のindex.jsです。.このファイルはURLから roomNameこのファイルはURLからHostまたはParticipantのインスタンスを作成します。その後、処理を初期化します。

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

アプリケーション・ロジックは ホストクラス参加者クラスで行われます。これらのクラスはコードの可読性を向上させるために追加ファイルを利用します。以下のファイルを確認できます。 私たちのアプリケーションが使用しているさまざまなファイル.

デバイスの選択

デバイスの選択は、両方のビュー(参加者とホスト)で実装される。MediaDevicesのAPIリファレンス MediaDevices APIリファレンスで説明されているように、私たちのアプリケーションは、通話中のデバイスの接続/切断を処理するのに十分な堅牢性を持たなければならない。

通話中にデバイスが抜かれたり、新しいデバイスが接続された場合はどうなりますか?私たちのアプリケーションは、利用可能なデバイスのリストの変更を検出するのに十分賢くなければなりません。イベントリスナーをセットアップして、利用可能なデバイスに変更があったときに、UIを更新して利用可能な最新のデバイスを表示する関数を呼び出すようにします。

まず、ユーザーがカメラやマイクの使用許可を与えたら、デバイス・リストを更新する最初のコールをトリガーします。これは accessAllowedイベントを利用します。

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

次に、通話中に利用可能なメディアデバイスに更新があった場合に、利用可能なデバイスを再計算するイベントリスナーを設定する。

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

この refreshDeviceList関数は、オーディオ・ビデオ・デバイスのリストをDOM要素に追加します。今回は簡単のためにドロップダウンメニューを使用します。この関数の詳細をご覧になりたい場合は、以下のソースをご覧ください。 関数の実装ソースコードをご覧ください。によって返された現在のオーディオ・ソースとビデオ・ソースに、HTMLのselectedタグを追加します。 getAudioSource()getVideoSourceによって返される現在のオーディオ・ソースとビデオ・ソースに、それぞれ

通話中にデバイスが変更された場合の処理には setVideoSourcesetAudioSourceをそれぞれ利用する。ここでは、そのうちの1つの処理について補足しておく。

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

ドロップダウンメニューの変更時にイベントリスナーを設定し、それをトリガーとして onVideoSourceChanged関数をトリガーします。この関数は、対象としているラベルのデバイスIDを探します。そして setVideoSourceメソッドを呼び出します。

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

ホストを待つ

アプリケーションは、参加するユーザーがホストなのか参加者なのかを知る必要がある。この場合、HostはすべてのParticipantを通話から切断することができるので、サーバーサイドからユーザーの役割に応じて異なるHTMLファイルを提供します。エントリーポイントは、ナビゲートしたURLに応じて、ホストまたは参加者をインスタンス化します。これは本番環境用のアプリケーションではないので、ルートで認証を実装する必要があることを覚えておいてください。

すべてのロジックは init関数 関数から始まります。にある関数index.jsから始まります。

この init関数は getCredentials関数を呼び出します。 関数を呼び出します。を呼び出します。

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

私たちのサーバーは管理者/ホストのためにモデレータートークンを、参加者のためにパブリッシャートークンを生成します。トークンの作成と役割の詳細については、以下を参照してください。 トークン作成に関するドキュメント.

サーバー側のトークン生成 サーバー側トークン生成.

トークンがクライアント側で受信されると、ホストかパーティシパントがセッションに接続したかどうかを知ることができます。 SDKによってディスパッチされる接続イベント.アプリケーションの流れは以下のようになります:

ホストがコールに参加する場合、彼らはセッションに接続し、すぐにパブリッシュを開始します。参加者がコールに参加する場合、コール前のテストを実行し、その後参加者がセッションに接続します。ホストがすでにセッションに接続している場合、参加者はパブリッシングを開始します。そうでない場合、参加者はホストが参加するまで接続されたままとなり、その後初めてパブリッシングが開始されます。

参加者

これが initの機能はこのようになります。最初に参加者(パブリッシャーロール)の認証情報を取得します。また、プレコールテストからの接続/ストリームによってメインセッションが汚染されるのを防ぐために、プレコールテスト用に別の認証情報を要求する。その後、プレコールテストを開始し(この方法は後で説明する)、テストが終了したら、セッションに接続する。

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

connect関数は、セッションにすでに接続しているホストがあるかどうかをチェックします。既にホストが存在する場合はパブリッシュを開始し、存在しない場合は接続を維持します。

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

この isHostPresent関数はホストがセッションに接続されていればtrueを返し、そうでなければfalseを返します。

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

この usersConnected配列はセッションの接続を追跡します。この配列は connectionCreatedイベントが発生すると connectionDestroyedイベントが発生するとデクリメントされます。新しい接続があると、この変数は両方のクラス(ホストと参加者)からインクリメントされることに注意することが重要です。したがって、この変数は両方のクラスからアクセスできる必要があります。

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

参加者がセッションに接続したときにホストが存在しない場合、新しいホストがセッションに参加するまで待ち、そのときに公開を開始します。

プレコールテスト

良いカスタマー・エクスペリエンスを提供するもう一つの重要な側面は、可能な限り円滑に物事が進むように、接続性と品質のチェックを実行することです。参加者が司会者の参加を待たなければならないのであれば、この貴重な時間を利用して、通話前のテストを実施してはどうだろうか。

を使用します。 ネットワークテストnpm モジュールを使用して、参加者が Vonage Video API ロギング、メッセージング、メディア、API サーバーに接続できることを確認します。ネットワークの動作は動的であるため、通話前の結果が良好であっても、通話中に利用可能な帯域幅が変化しないことを保証するものではないことにご留意ください。

簡略化のため、ここでは参加者のみでプレコールテストを実行し、ホストでは実行しない。もちろん、両方で実行することもできます。

接続性と品質テストからのレスポンスを処理するためにいくつかのファイルを作成し、テストのステータスを示すプログレスバーも作成した。単純な プログレスバーで、テストが完了するのにかかる時間である30秒後に満たされます。NetworkTest をインスタンス化するときにタイムアウト値を設定することで、これを変更することができます。しかし、テストが長く実行されればされるほど、結果はより正確になります。テストが失敗した場合は、進捗インジケータも削除します。

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

ネットワーク・テストの実装をご覧になりたい方は、以下のファイルをご覧ください。 をご覧ください。.

プレコールテストの結果は、推奨される解決策と MOSスコアを0から4.5まで提示する。

これは少々主観的であるため、MOSスコア(Good、Bad、Excellent...)に基づいて、好ましい解像度と結果ラベルを表示するかどうかを決定する機能を追加する予定です。

推奨解像度とスコア・ラベルを含めるかどうかは、/src.variables.jsの変数 addFeedback変数を切り替えることで、推奨解像度とスコアラベルを含めるかどうかを決めることができます。また モジュールのErrorNamesを利用することもできます。

次はどうする?

完成したプロジェクトは GitHubで公開されています。また、Vonage Video API の詳細については ドキュメント.

シェア:

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.