ビデオ通話のトラブルに対する実装のコツ

テレワークというキーワードが盛んに飛び交っています。
テレワークの必須ツールとしてビデオ通話がありますが、仕事の場面以外にもビデオ通話の活用が広がってきています。

どのビデオ通話アプリも優秀ですが、やはり利用者によってはうまく利用できていないケースもあります。アプリ提供者も問い合わせの対応に追われる場面もよく見受けられます。

この記事では様々な利用者、環境に応じて発生する問題に対してどのような実装をすればよいかをつらつらと記載します。主にブラウザ版(WebRTC)のビデオ通話アプリについてです。

この記事に記載の実装は以下にまとめて公開してます。
[こちら](https://github.com/fu-jimoto/AgoraIO-WebRTC-Troubleshooting)
SDKに付随するサンプルをカスタマイズしています。

### 2022/06/01更新
WebSDK4.X.X用にサンプルを作成しました。
[AgoraIO-WebRTC-Troubleshooting-V4](https://github.com/fu-jimoto/AgoraIO-WebRTC-Troubleshooting-V4)

## 利用環境チェック
実際のビデオ通話を始める前に利用できるかの確認は重要です。
擬似的にビデオ通話を行う事で問題点をユーザーに認識させる事ができます。

利用環境チェックについては以下にサンプルが公開されています。
[こちら](https://github.com/AgoraIO/Tools/tree/master/TroubleShooting/Agora-WebRTC-Troubleshooting)
利用環境チェックについてはこのサンプルを用いて解説します。

### ブラウザのチェック
ブラウザ版の場合、そもそもWebRTCに対応しているかどうかがポイントになります。
agora.io SDKではブラウザチェックのAPIが用意されています。

```javascript:App.vue
    handleCompatibilityCheck() {
      this.currentTestSuite = "0";
      let testSuite = this.testSuites["0"];
      setTimeout(() => {
        testSuite.notError = AgoraRtc.checkSystemRequirements();
        testSuite.notError
          ? (testSuite.extra = this.t("fully_supported"))
          : (testSuite.extra = this.t("some_functions_may_be_limited"));
        this.handleMicrophoneCheck();
      }, 3000);
    },
```
AgoraRTC.checkSystemRequirements()がチェックするAPIになります。
falseが返却された場合にアラート表示して、ビデオ通話画面へ遷移させない処理が有効かと思います。

### マイク・カメラのチェック
次にマイク・カメラが利用できるかがポイントになります。
まずはマイクのチェックです。

```javascript:App.vue

    handleMicrophoneCheck() {
      this.currentTestSuite = "1";
      let testSuite = this.testSuites["1"];
      this.sendStream = AgoraRtc.createStream({
        streamID: this.sendId,
        video: false,
        audio: true,
        screen: false
      });
      this.sendStream.init(
        () => {
          this.sendStream.play("test-send");
          let totalVolume = 0;
          this.microphoneCheckTimer = setInterval(() => {
            this.inputVolume = Math.floor(
              this.sendStream.getAudioLevel() * 100
            );
            totalVolume += this.inputVolume;
          }, 100);
          setTimeout(() => {
            clearInterval(this.microphoneCheckTimer);
            this.sendStream.close();
            if (totalVolume < 60) {
              testSuite.notError = false;
              testSuite.extra = this.t("can_barely_hear_you");
            } else {
              testSuite.extra = this.t("microphone_works_well");
            }
            this.handleSpeakerCheck();
          }, 7000);
        },
        err => {
          // do next test
          testSuite.notError = false;
          testSuite.extra = err.msg;
          try {
            this.sendStream.close();
          } catch (error) {
            throw(error);
          } finally {
            this.handleSpeakerCheck();
          }
        }
      );
    },
```
AgoraRtc.createStream()で実際にストリームを作成し、getAudioLevel()で入力ボリュームをチェックしています。

次にカメラのチェックです。

```javascript:App.vue
    checkProfile(profile) {
      return new Promise((resolve, reject) => {
        this.sendStream && this.sendStream.stop();
        this.sendStream = AgoraRtc.createStream({
          streamID: this.sendId,
          video: true,
          audio: true,
          screen: false
        });
        this.sendStream.setVideoProfile(profile.enum);
        this.sendStream.init(
          () => {
            this.sendStream.play("test-send");
            setTimeout(() => {
              let videoElement = document.querySelector("#video" + this.sendId);
              if (
                videoElement.videoWidth === profile.width &&
                videoElement.videoHeight === profile.height
              ) {
                profile.status = "resolve";
                resolve();
              } else {
                profile.status = "reject";
                reject("Resolution mismatched");
              }
            }, 1000);
          },
          err => {
            reject(err);
          }
        );
      });
    },
```

カメラのチェックも実際にストリームを作成し、再生をさせて期待されたVideoElementが生成されたかを判定しています。


### 低帯域用の対応
混雑しているWi-Fi環境や、月末に帯域制限がかかっている4G回線等はビデオ通話に必要な帯域が不足します。
その場合は、「映像の画質を落とす」「音声のみにする」という方法があります。最低限のコミュニケーションを保つという考え方です。

```javascript:index.html
      rtc.client.enableDualStream(function() {
        console.log("enable dual stream success.");
      }, function(err) {
        console.log("dual stream error."+err);
      });
      //中略
      var highOrLow = 0;
      $("#switchVideo").on("click", function (e) {
        console.log("switchVideo")
        e.preventDefault();
        if (highOrLow === 0) {
          highOrLow = 1
          console.log("Set to low");
        }else {
          highOrLow = 0
          console.log("Set to high");
        }
        rtc.client.setRemoteVideoStreamType(rtc.remoteStreams[0], highOrLow);
      });
```
こちらは手動で画質を切り替えるサンプルになります。
自動で切り替えるには以下のAPIを利用します。

```javascript

client.setStreamFallbackOption(remoteStream, fallbackType);
```
fallbackTypeによって以下の振る舞いになります。
0:自動切り替え無効化
1:低画質に切り替え
2:低画質でも厳しい場合に音声のみ受信に切り替え

又、低画質の設定はデフォルトで90P程度ですが、指定する事も可能です。

```javascript:index.html
rtc.client.setLowStreamParameter({bitrate:65, framerate:15, height:120, width:160});
```

### ネットワーク環境の可視化
ビデオ通話中にもネットワーク環境の良し悪しを明示的に可視化する事も重要です。

```javascript:index.html
      rtc.client.on('network-quality', function(stats) {
        console.log('downlinkNetworkQuality', stats.downlinkNetworkQuality);
        console.log('uplinkNetworkQuality', stats.uplinkNetworkQuality);

        if(stats.downlinkNetworkQuality == 0){
          $("#networkQuality").html("NetworkQuality:-");
        }else if(stats.downlinkNetworkQuality == 1 || stats.downlinkNetworkQuality == 2){
          $("#networkQuality").html("NetworkQuality:Good");
        }else{
          $("#networkQuality").html("NetworkQuality:Bad");
        }
      });
```
client.on('network-quality',funciton(){})で6段階の評価が可能です。
このAPIはRTTやパケットロス、ジッター 等でネットワーク環境の品質をチェックしています。

## 事後の確認
ビデオ通話で問題が発生し、事後に問い合わせがくるパターンもあります。
その場合に求められるのは原因です。システム側では問題がなく、ユーザーの環境に起因している事を示す必要があります。

### 録画データでの確認
agora.ioでは録画の仕組みも提供しています。クラウド型とオンプレミス型になります。
実装方法は以下に解説があります。
https://qiita.com/v-cube/items/bd119f39239b6e6a7c22
https://qiita.com/v-cube/items/6eeefe73d5c9ecc6d5a2
「映像が見えなかった」という問い合わせに対して録画データを確認し、録画データに映像が残っていなかった場合は送信側の問題、映像が残っていれば受信側の問題という具合に切り分けが可能です。

### CallSearchの活用
Call SearchはAgora Analyticsの機能の1つで、通話時の品質をデータ化し、それを視覚的にわかりやすく表示した機能です。

例えば、予期せぬ切断や音声途切れなど、通話の品質が良くなかった場合の解析用として通話の履歴、品質を確認することができます。

サポートセンターのFAQにCall Searchの説明・活用ガイドをまとめております。
https://vcube-support.zendesk.com/hc/ja/articles/900000553886



## 最後に
[**agora.ioに関するお問い合わせはこちらから**](https://jp.vcube.com/lp/agora?utm_campaign=Agora&utm_source=Qiita&arc_id=fa14d2cbfd3a1f07ce19)
[<img width="912" alt="Agoraの詳細はこちら" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300377/5865441c-9d31-c702-3698-3aeb84927f28.png">](https://jp.vcube.com/lp/agora?utm_campaign=Agora&utm_source=Qiita&arc_id=dd30a977b68855ceeb64)