CPaaSのVonageとReactでビデオ通話アプリを作る

[最新CPaaSコミュニケーションAPIを比べる or 使ってみよう！ 【PR】Vonage Japan](https://qiita.com/advent-calendar/2021/vonage2021) の22日目です。

## CPaaSとは

ビデオ通話がしたい場合、ZoomやGoogle Meet、Microsoft Teamsなどを利用するのが一般的ですね。これらのサービスは **SaaS** （Software as a Service）に分類されます。

ZoomなどのSaaSを使うことで、手軽にビデオ通話ができます。しかし自由にカスタマイズしたり、他のシステムに組み込むことは難しいです。

では既存システムにビデオ通話機能を組み込みたい場合などは、どうするか。そんなときに便利なのが、Vonageなどの **CPaaS** です。CPaaSは「Communication Platform as a Service」の略で、ビデオ通話などのコミュニケーションサービスを提供するクラウドAPIです。

CPaaSには今回使うVonageの他に、TwilioやAgoraなどもよく使われます。これらのサービスはビデオ通話だけでなく、音声・メッセージ（SMS・チャット）など様々なコミュニケーション機能をAPIやSDKとして提供しています。

## 完成形

この記事ではVonageのVideo APIを使って、簡単なビデオ通話アプリを作ります。以下が完成イメージです。
![React_App_1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140258/191b6802-25cc-f43e-a24c-2acf9f6a0615.jpeg)


画面共有もできます。
![React_App_2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140258/df0a5a90-426d-4584-f3b6-3dbc5901acc9.jpeg)


大人数でも使えます。
![React_App_3_2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140258/a3a5ebd9-1ed4-b0a9-c7af-6d2dcce919a3.jpeg)

コードは以下で公開しています。
https://github.com/miya-start/react-tokbox

## Vonageを選んだ理由

CPaaSの中でVonageを選んだのは、専用のReactコンポーネントがライブラリとして公開されているためです。[opentok-react](https://github.com/opentok/opentok-react)というライブラリです。このライブラリが提供するReactコンポーネントを使うことで、簡単にビデオ通話アプリを作れます。

ただし実際に使ってみると、opentok-reactの利用は良い点ばかりではありませんでした。簡単にアプリを作れるという利点はありますが、UIをカスタマイズするのが難しいなどの問題点もあります。くわしくは後で触れます。


## 対象読者

- （前提） Reactで簡単なアプリを作れる
- CPaaSのAPIを使って、ビデオ通話を試してみたい
- ビデオ通話アプリをReactで作りたい
- 3人以上参加できるビデオ通話アプリを作りたい

## 必要な環境

- Nodeはバージョン10以上、npmはバージョン5.2以上がインスールされていること

それぞれのバージョンは `node -v` と `npm -v` で確認できます。

# 実際に作ってみる

それでは実際に作っていきます。大まかな流れは以下のとおりです。

1. Vonageのアカウントと「プロジェクト」を作る

2. `create-react-app` でReactアプリを作る

3. 1と2をもとにReactコンポーネントを作る

### Vonageのアカウントなど

まずはVonageのVideo APIを利用するためにアカウントを作成します。アカウント作成は[こちら](https://tokbox.com/account/user/signup)からできます。アカウントには10ドルのクレジットが付与されるため、無料でAPIを試せます。

:::note warn
VonageではVideo APIを提供するアカウントと、それ以外のコミュニケーションAPIを提供するアカウントが別になっています。ビデオ通話（WebRTC）関連のAPIは、もともとTokBoxという企業によって開発されました。そのためVideo APIサービスのドメイン名はtokbox.comとなっています。TokBoxという表記があれば、VonageのVideo APIを指すと捉えれば良いでしょう。
:::

アカウントを作成してサインインしたら、「プロジェクト」を作ります。このプロジェクトごとに、Video APIを利用するためのAPI KEYなどが生成されます。プロジェクトを作るには、左側のメニューにある「Create Project」を選択します。するとダイアログ（モーダル）が表示されるので、「Create Custom Project」を選択します。
![Vonage_Video_API_Account_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140258/93e66ad4-74ed-4643-b0eb-df1623874ff0.png)
↓
![Vonage_Video_API_Account_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140258/671dbf7b-22fd-bc99-0c96-6a4a89bd64bd.png)


次に作成するプロジェクトの名前を入力します。何でも構いません。ここでは動画のコーデックも選べますが、デフォルトのままで良いでしょう。ちなみにコーデックというのは、圧縮（compress）や解凍(decompress)をするプログラムのことです。
![Vonage_Video_API_Account_5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140258/492f3ead-0529-d61c-52fc-9798e16baf42.png)

上記の「Create」ボタンを選択すると、プロジェクトが作成できます。作成できたら、下記の「View Project」を選択してプロジェクトを表示します。
![Vonage_Video_API_Account_6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140258/95c2ea7a-7237-8d8a-5fa5-be54eb951905.png)

表示されたプロジェクトの上部に「PROJECT API KEY」が記載されています。これを後で使います。
![Vonage_Video_API_Account_7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140258/baf83d0a-982e-ae0b-f21a-5507ff827113.png)

プロジェクトページを下にスクロールすると、「Project Tools」という項目があります。ここで「Create Session ID」というボタンをクリックして、Session IDを作成します。Sessionというのは、ビデオ通話の参加者が会話などをするための「仮想的な部屋」だと考えると良いでしょう。
![Vonage_Video_API_Account_8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140258/15cbd21f-6eb8-7a0d-acbf-062da2d2e41e.png)

次に、Sessionに接続するためのToken（トークン）を生成します。先ほど生成したSession IDをコピーして、その下にある入力フォームに貼り付けます。「Expiration Time」は、Tokenの有効期間です。この有効期間を過ぎると、Sessionに接続できなくなります。

Session IDを貼り付けて、Expiration Timeを設定したら、「Generate token」ボタンをクリックします。
![Vonage_Video_API_Account_9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140258/cabc0eba-a4e9-b1c8-7551-0ccc9b2b990b.png)

生成したTokenとSession IDは、後で使います。

### Reactアプリを作る

Reactアプリを作っていきます。ローカルの任意のディレクトリで、以下のコマンドを実行します。`react-tokbox` の部分は自由に変更してください。アプリを作成するディレクトリの名前ですね。

```bash
npx create-react-app react-tokbox
```

コマンドの実行が完了したら、作成されたディレクトリに移動します。そして必要なモジュール（ライブラリ）をインストールします。

```bash
cd react-tokbox && npm install opentok-react --legacy-peer-deps
```

上記のコマンドでは、`--legacy-peer-deps` というフラグを指定しています。このフラグが必要なのは、モジュールのバージョンがコンフリクト（衝突）してしまうためです。

npxでインストールした `create-react-app` は、Reactのバージョン17に依存しています。一方で `opentok-react` は、Reactのバージョン16に依存しています。そのため `--legacy-peer-deps` フラグを付けずにインストールしようとすると、コンフリクトによりインストールできません。

ただしこのフラグが必要になるのは、npmのバージョン7からです。npmのバージョン5や6を使っている場合は、フラグを付けなくてもインストールできます。

この問題について詳しくは、以下が参考になりました。

- [What does npm install --legacy-peer-deps do exactly? When is it recommended / What's a potential use case?](https://stackoverflow.com/a/66620869/9281331)

### APIキーなどを読み込む

先ほどVonageのプロジェクトで作成したPROJECT API KEY、SESSION ID、TOKENを、Reactアプリで使います。ここでは以下のように、ルートディレクトリの `.env` ファイルに保存しました。

```.env
REACT_APP_TOKBOX_API_KEY=XXXX
REACT_APP_TOKBOX_SESSION_ID=XXXX
REACT_APP_TOKBOX_TOKEN='XXXX'
```

`.env` ファイルに保存したキーとバリューのペアを、Reactアプリから `process.env.REACT_APP_XXX` 経由で読み込みます。たとえば以下のように。

```src/App.jsx
function App() {
  //省略
  return (
    <OTSession
      apiKey={process.env.REACT_APP_TOKBOX_API_KEY}
      sessionId={process.env.REACT_APP_TOKBOX_SESSION_ID}
      token={process.env.REACT_APP_TOKBOX_TOKEN}
      // 省略
    >
    </OTSession>
  )
}
```

### opentok-reactのコンポーネント

opentok-reactのコンポーネントをインポートします。このコンポーネントの構成などは、以下の記事が参考になりました。

- [Create Custom Video Chat with React Components and Vonage](https://learn.vonage.com/blog/2019/07/16/opentok-react-components-dr/)

この記事とopentok-reactの[README](https://github.com/opentok/opentok-react#opentok-react)を基にコンポーネントをインポートすると、簡単にビデオ通話アプリが作れました。

しかし、ここで問題が起きます。それはビデオ通話の参加者が3人以上になると、UIが崩れてしまうことです。具体的には、3人目以降の参加者がカラム落ちしてしまい、スクロールしないと参加者全員が見れなくなったりします。あるいは、以下のような縦長の表示になってしまいます。
![React_App_5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140258/ef9ace74-6c88-e62d-e878-194c91ea3d19.jpeg)

このようなUIの問題を修正するためには、`opentok-react` からインポートしているコンポーネントに手を加える必要がありました。具体的には、コンポーネントの要素にインラインスタイルなどで `width` や `height` を指定する必要があります。

そこで `opentok-react` からインポートしているコンポーネントを、いったんReactアプリの `src/components` ディレクトリにコピーしました。`src/components` 内のコンポーネントの構成は、最終的に以下となりました。
![CustomOTStreams_js_—_react-tokbox.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140258/1eb2e2a7-daac-9f8c-05d5-75ac25c63355.png)
先頭に `Custom` と付いているコンポーネントが、`opentok-react` からコピーしたコンポーネントです。コンポーネントのコードは以下から確認できます。

- https://github.com/miya-start/react-tokbox/tree/main/src/components

### ビデオ表示のレイアウト

ビデオ通話の参加者が3人以上になったとき、各参加者のビデオ表示をどう配置するか。

たとえば参加者が5人のときは、1行目に3人、2行目の真ん中に2人を配置すると、良い感じのレイアウトになります。
![React_App_3_3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140258/72870259-88ef-52ff-6d6d-ade1b2afa21f.jpeg)

このような良い感じのレイアウトにするためには、参加者の人数から次の2つを決める必要があります。

- ① １つの行に、最大何人を配置するか
- ② 行数

たとえば上記のように、参加者が5人の場合。このとき1つの行に配置するのは、最大で **3人** です。1行目に3人、2行目に2人なので。そして行数は **2行** です。この①と②の値をもとに、各ビデオ表示コンポーネントの 横幅(`width`) と 高さ(`height`)を計算します。

この①と②の２つの計算は、以下のように行いました。

```src/contexts/subscriber-number-context.js
function useCalcVideoSize() {
  const subscriberNumber = useSubscriberNumberState()
  const videoNumber = subscriberNumber + 1
  const columnNumber = Math.ceil(Math.sqrt(videoNumber))
  const rowNumber = Math.round(Math.sqrt(videoNumber))
  // 省略
```

このコードの `columnNumber` が①の値になります。そして `rowNumber`が②の値です。`videoNumber` は、ビデオ通話の参加者数です。参加者数の平方根を求め、その値の小数点以下を切り上げるか、四捨五入するかしています。

実際のコードは以下にあります。

- https://github.com/miya-start/react-tokbox/blob/main/src/contexts/subscriber-number-context.js#L63

実際のコードでは、フッター部分にメニューの配置スペースを確保するための計算なども行っています。

上記で計算した①と②の値をもとに、ビデオ表示コンポーネントの横幅と高さを計算します。そして計算した値を、インラインスタイルの `width` と `height` で指定します。以下のように。

```src/components/CustomOTPublisher.js
  render() {
    const { className, style } = this.props
    return (
      <div
        className={className}
        style={{
          width: this.props.videoWidth,
          height: this.props.videoHeight,
          ...style,
        }}
```

このように横幅と高さを指定することで、ビデオ通話の参加者数が多い場合でも良い感じにレイアウトできるようになりました。ただし参加者が数十人くらいの数になると、各ビデオ表示がとても小さくなってしまいます。そのためブラウザ(Window)のサイズによって、各ビデオ表示が小さくなってしまう場合はデフォルトで表示する参加者の数を絞る必要があるでしょう。

また画面共有したら、その画面を自動で大きく表示するなどの機能もあると便利です。

## おわりに

冒頭でも少し触れましたが、opentok-reactが提供するReactコンポーネントの利用は良い点ばかりではありませんでした。簡単にビデオ通話アプリを作れるという利点はありますが、UIをカスタマイズするのが難しいです。またReact 17がリリースされている2021年現在ではレガシーなAPIが使われていたり、Functional ComponentではなくClass Componentが使われていたりします。

そのため振り返って考えてみると、opentok-reactを使わない方が良かった気がします。VonageにはReactに限らず、JavaScriptでVideo APIを利用するためのライブラリがあります。[OpenTok.js](https://tokbox.com/developer/sdks/js/)というライブラリです。

なので今後はOpenTok.jsや、他のCPaaSを試してみたいと思います。

# 参考一覧

この記事に関連する参考ページを、トピック別にまとめました。

### CPaaS

- [Vonage](https://www.vonagebusiness.jp/communications-apis/)
- [Twilio](https://www.twilio.com/ja/)
- [Agora](https://jp.vcube.com/service/agora)

### VonageのVideo API

- [アカウント作成](https://tokbox.com/account/user/signup)
- [opentok-react](https://github.com/opentok/opentok-react)
- [Create Custom Video Chat with React Components and Vonage](https://learn.vonage.com/blog/2019/07/16/opentok-react-components-dr/)
- [OpenTok.js](https://tokbox.com/developer/sdks/js/)
- [今回作成したビデオ通話アプリのコード](https://github.com/miya-start/react-tokbox)

### その他

- [What does npm install --legacy-peer-deps do exactly? When is it recommended / What's a potential use case?](https://stackoverflow.com/a/66620869/9281331)