📌

オンライン通話に入る前に、マイクが利用可能かをチェックしたい

2024/11/01に公開

はじめに

オンライン通話をしているとき、以下のようなやりとりをすることが多いかと思います。
「聞こえますか?」
「聞こえません」
聞こえないときは、さまざまな原因が考えられますが、一度通話に参加してしまうと原因が分からず、対応が難しくなることも多いでしょう(一般ユーザーだけでなく、エンジニアでも難しい場合があります)。

今回は、自作のオンライン通話システムを実装する際に、通話者のマイクが動作しているか否かを、相手が入室する前に確認する方法と、マイクが動作していない場合、相手にそれを事前に伝える方法を紹介いたします。

前提

実行環境:ブラウザ
コード言語:JavaScript
本処理を行った後、 WebRTCでの通話に移ることを前提としています。

マイクの利用許可を取得する

まず、基本となるブラウザのマイク利用許可の取得方法です。これは、多くのサイトでも行われている一般的な手順です。

以下は、JavaScriptでマイクの利用許可を得るコードです。

navigator.mediaDevices.getUserMedia(constraints)
.then(()=>{
 // 利用許可ok
}).catch(()=>{
// 利用許可NG
})

マイク利用がブロックされている場合、それを相手に伝えて解除してもらう

Chromeの場合、一度ユーザーにブロックされると、その記録が残り、再度「マイクの利用許可」を求めてもダイアログが表示されず、許可が得られなくなってしまいます。通話サービスでは「マイク利用許可NG」は致命的ですので、ブロックされている場合は、ユーザーに「明示的に解除する」よう通知を出す必要があります。


「利用許可NG」からの回復方法はブラウザごとに異なるため、ブラウザの種類を判定し、それぞれの解除方法を案内する必要があります。PCではあまりブロックされることはありませんが、Android Chromeでブロックされるケースが多いようです。一方、Safariでは許可状況が保存されず、毎回ユーザー確認が行われるため、この処理は不要です。

マイクのブロックが解除された場合の処理

マイクのブロックが解除されたら次の処理に移りたいところですが、「ブロックが解除された」というイベントを検知する手段はJavaScriptにはないようです。そのため、ブロックされたことを検知した後、定期的にgetUserMediaをチェックし、許可が得られたら次の処理に進む仕組みを構築する必要があります。。

setInterval(() => {
    navigator.mediaDevices.getUserMedia(constraints)
    .then(()=>{
     // 利用許可ok
    }).catch(()=>{
    // 利用許可NG
    })
}, 500)

以前はこのように、定期的にポーリングすることでブロック解除を検知していましたが、現在では、ユーザーに設定変更を反映させるためリロードを促すダイアログが表示されるようです(他のブラウザでの挙動は別途確認が必要です)。



ここまでが、マイクの許可を取得する流れです。しかし、マイクの許可が得られても、音声が聞こえないことはよくあります。例えば、マイクが故障している、または音量が小さすぎるなどのケースです。そのため、ルームに入る前に発声してもらい、ある程度の音量が拾えるかをチェックします(同時に、音量を波形で表示します)。

マイクの音量を表示する

以下のようにマイクの波形を表示します。

getUserMediaで取得した音声ストリームを用いてMediaStreamAudioSourceNodeオブジェクトを生成し、AnalyserNodeを作成した上で、audioInputをanalyserNodeに接続します。

navigator.mediaDevices.getUserMedia(constraints)
.then((stream) => {
thies.audioContext = new window.AudioContext()
const audioInput = this.audioContext.createMediaStreamSource(stream)
this.analyserNode = this.audioContext.createAnalyser();
this.analyserNode.fftSize = 2048;
audioInput.connect(this.analyserNode);

その波形データをcanvasに表示します。

window.setInterval(() => {
    const soundArr = new Uint8Array(this.analyserNode.fftSize);
    this.analyserNode.getByteTimeDomainData(soundArr);

    const barWidth = this.canvas.width / soundArr.length;
    this.drawContext.fillStyle = 'rgba(255, 255, 255, 1)';
    this.drawContext.fillRect(0, 0, this.canvas.width, this.canvas.height);
    for (let i = 0; i < soundArr.length; ++i) {
      const sound = soundArr[i];
      const percent = ((sound - 127) * 2 + 127) / 255;
      const height = this.canvas.height * percent;
      const offset = this.canvas.height - height;
      this.drawContext.fillStyle = 'blue';
      this.drawContext.fillRect(i * barWidth, offset, barWidth, 2);
    }
}, 100);

音量が閾値を超えたら入室に移行する

先ほどのコードで取得したsoundArrの最大音量が閾値を超えているかチェックし、超えていた場合に入室できるようにします。

let valueMax = 0;
for (let i = 0; i < soundArr.length; ++i) {
  const sound = soundArr[i];
  valueMax = valueMax < sound - 127 ? sound - 127 : valueMax;
}
if (AUDIO_THRESHOLD_SUFFICIENT < valueMax) {
   入室プロセスに移行

閾値AUDIO_THRESHOLD_SUFFICIENTの値は、プロジェクトによって調整が必要です。私たちのプロジェクトでは、この値を6に設定しています。

次に書きたいこと

音声の録音方法、音声形式の変換など、web audio apiを用いて行うことを書いていきたいと思います。

WED Engineering Blog

Discussion