Vue3 + WebRTC でブラウザからカメラを扱う簡単なデモの紹介

こんにちは。ブロックチェーン事業部の熊谷です。
普段はWebアプリケーションのフロントエンド開発に携わっています。

とあるプロジェクトでWebRTCという、Webブラウザからリアルタイム通信を行うことができる技術に触れる機会がありました。

webrtc.org

そこで今回はそれらの知識整理も兼ね、カメラやマイクを扱うWebRTCのAPIを使い、簡単なデモを作成してみました。

コードの全体はGitHubに公開しているので気になる方はチェックしてみてください。
また、記事中に載せているコードの上に記載されている【ファイル名】をクリックするとCodeSandboxが開きますので、すぐ確認したい方はそちらをチェックしてみてください。

github.com

WebRTCについて

最近はテレワークの拡大につき、ZoomやTeamsのようなビデオ会議システムの利用が広まっていますね。
ブラウザやモバイル端末ではWebRTC(Web Real-Time Communication)という技術のAPIを使うことで、手軽に似たようなアプリケーションを作ることができます。
また、WebRTCには公式のサンプル集があるので、比較的学習もしやすいと思います。

webrtc.github.io

しかし、実際のプロジェクト(Vue3 + TypeScript の環境)で取り組んだ際に少し詰まってしまったことがあったので、そのコツなどを共有できればと思います。

Vue3について

フロントエンドフレームワークのVueというのは聞いたことがある方が多いと思いますが、 今回使用しているバージョン3(以下Vue3)は、2020年9月に正式リリースされたものになります。

v3.ja.vuejs.org

Vue3 からは TypeScript との親和性の強化や、Composition API というロジックの再利用性を向上させる仕組み(React の Function Component のようなもの)などが追加されています。
今回のデモでも TypeScript と Composition API を使っていきますが、Vue2 以前の書き方や、今までのような Class ベースの書き方から結構変わるので、これから Vue3 を使いたいと考えている方の参考になれば幸いです。

カメラ画像の取得

WebRTCでは、カメラの映像やマイク音声は mediaDevices.getUserMedia メソッドを実行するだけで取得することができます。

developer.mozilla.org

初回のみ、このメソッドを実行した際にカメラやマイクなどのメディアデバイスへアクセスする許可をユーザに求めるダイアログが表示されます。
メディアデバイスへのアクセスが許可された場合のみ、カメラ映像やマイク音声が取得されます。

カメラ映像を取得するだけであれば、これくらいのコードで実装できます。

Camera.vue ← Click to open sandbox

まず、Vue3のコンポーネントの定義について少し説明します。

コンポーネントの定義 defineComponent (※a1) で行います。
(TypeScript を使用する場合です。JavaScript を使用している場合は defineComponent を使用せず、ただオブジェクトを定義するだけでOKです。)
このコンポーネント内で実行したい処理やロジックは、基本的に setup 関数 (※a2) 内に定義していきます。
setup で定義した変数や関数のうち <template> 内で使用したいものは、setup 関数の return 文 (※a3) で返却するオブジェクトに登録することで使用できるようになります。

次に、カメラの映像を取得する getUserMedia を実行する部分を見ていきます。

getUserMedia の引数には、音声・映像をどのような条件で取得するかを指定するための MediaStreamConstraints オブジェクトを指定します (※a4)。

developer.mozilla.org

getUserMedia は 取得した映像と音声のトラックが格納された MediaStream オブジェクトで解決される Promise を返します。

developer.mozilla.org

取得された MediaStream は captureStream 変数に保存し、return するオブジェクトに登録しています。

reactive と ref について

setup 関数内では、状態に合わせて内容が変化する変数を定義するために reactive、 もしくは ref を使用する必要があります。
reactive と ref は似た機能を提供するメソッドのため使いどころに迷うことが多いと思います。
ベストプラクティスというわけではなく、私の使い分けの判断基準ですが、以下のように使い分けています。

  • reactive: Composition API を使わない場合の data 定義のように、オブジェクト内にプロパティを1つずつ定義する場合に使用
  • ref: 単一の値、もしくは既にプロパティが定義済みのオブジェクトを保存するのに使用

※ ref を使う場合ですが、その値を取得・設定するには変数名に .value を付けてアクセスする必要があります。

最後に、取得された MediaStream を <video> 要素に設定します (※a5) 。
MediaStream オブジェクト は video 要素の srcObject 属性に設定します。
(video要素内で値をバインドする場合は srcObject.prop で指定できます。)

video 要素に指定している他の属性について

autoplay は MediaStream を自動的に再生するために必要となります。
playsinline は video 要素がある場所で再生させることを明示的に示す属性で、スマートフォン(特にiPhone) のような端末でレイアウトを崩さずに再生するために必要となります。
muted はビデオの音声をミュートにして再生する属性で、音声を出力させたい場合は指定してはいけない属性ですが、今回はハウリング対策のため指定しています。外すとどうなるか気になる方は、試しに外してみてください。

端末で利用できるメディアデバイスの取得

ほとんどのビデオ会議システムには使用するカメラやマイクを選択する機能がありますよね。次はそれを実装してみます。
WebRTC では mediaDevices.enumerateDevices メソッドを実行することで、端末に接続されているメディアデバイスの一覧を取得することができます。

developer.mozilla.org

このデバイス選択の機能を実装したコードはこのようになります。

DeviceSelection.vue ← Click to open sandbox

enumerateDevices メソッドは、取得したメディアデバイスの情報が格納された MediaDeviceInfo オブジェクトで解決される Promise を返します (※b1)。

developer.mozilla.org

取得された MediaDeviceInfo オブジェクトは gotDevices 関数 (※b2) により deviceList 変数に格納されます。
その後、Vue の computed メソッドを利用し、算出プロパティとしてドロップダウンリストの項目データを生成しています (※b3) 。

enumerateDevices で取得されるデバイスの種類について

enumerateDevices で取得されるデバイスの種類は

  • 音声入力デバイス
  • 音声出力デバイス
  • 映像入力デバイス

の3種類あり、それぞれ MediaDeviceInfo の kind パラメータに audioinput, audiooutput, videoinput として登録されているため、これを確認することで判別できます。

算出プロパティで生成したドロップダウンリストの項目データを setup 関数の return で返却し、それを基に ドロップダウンリストの項目を Vue でおなじみの v-for を使用して生成する (※b4) という流れになります。

選択したメディアデバイスへの切り替え

最後に、ドロップダウンリストで選択したデバイスを利用できるようにしてみましょう。
次に示すコードは先ほどの Camera.vueDeviceSelection.vue のコードを組み合わせて手を加えたものになります。

MediaDevice.vue ← Click to open sandbox

サイトにアクセスした際にカメラ映像を取得したりデバイス情報を取得する処理を実行するのは同じですが、このコンポーネントでは onMounted (※c1) という、コンポーネントが表示された際に処理を実行するライフサイクルフックを使用し、同様の処理を行っています。

v3.ja.vuejs.org

ここではドロップダウンリストの項目を変更した場合の処理内容を追っていきます。

まずはドロップダウンリストで選択した値を取得するため、select 要素の change イベントのハンドラを設定します (※c2) 。
Vue ではおなじみですが、DOMイベントに JavaScript のコードを紐づける場合は v-on を使用してイベントハンドラを指定します。
(今回は v-on の省略記法として、イベント名の前に @ を付ける方法を採用しています。)

デバイスの種類によって設定方法が変わってくるので、まずは音声・映像入力デバイスに設定している onSelectedValueChange (※c3) の処理を追っていきます。

onSelectedValueChange が実行されると、ページ初期表示時に実行されたのと同じく、カメラ映像の取得処理(ここでは start 関数)が実行されます (※c4) 。
start 関数内では、ドロップダウンリストで選択されているデバイス情報を取得し、そのデバイスIDを基に getUserMedia の引数に渡す MediaStreamConstraints オブジェクトを生成します。
生成したオブジェクトを getUserMedia の引数に設定して実行することで、指定したデバイスを使用した MediaStream を取得します。

新しい MediaStream を取得する前にトラックを停止する

start 関数の最初で stopTracks 関数を使用し、現在 video 要素に指定している MediaStream の映像・音声トラックを停止させています。
新しい MediaStream を取得する前に再生中の MediaStream を停止しないと、その MediaStream が参照されなくなってもメディアデバイスへのアクセスを保持したままとなってしまいます。
そのため新しい MediaStream を取得する前に古い MediaStream を停止するようにします。

start 関数終了後、最新のデバイス情報を取得するためにもう一度 getDevices 関数を実行して、音声・映像デバイスの切り替え処理が完了です。

ここまでは入力デバイスの切り替え処理でしたが、次は音声出力デバイスの切り替え処理(onAudioOutputChange)を追っていきます。

onAudioOutputChange が実行されると video 要素に音声出力デバイスを設定する処理(ここでは attachSinkId 関数)が実行されます (※c5) 。
この処理ではドロップダウンリストで選択されているデバイス情報を取得し、そのデバイスIDを video 要素の setSinkId メソッドを使用して sinkId パラメータに設定します。

developer.mozilla.org

各ブラウザにおける setSinkId の実装状況について

この sinkId 関係のメソッドやパラメータは、この記事の作成時点(2021年6月)ではまだ実験的機能という位置づけとなっています。
そのため、TypeScript の型定義ファイルに sinkId の記載が無い場合があるため、ここでは video 要素を any 型として扱っています。
また、使用しているブラウザによっても実装状況が異なるので、setSinkId を実行する前にメソッドが存在するかをチェックする必要があります。

setSinkId メソッドが存在していれば、引数に音声出力デバイスのデバイスIDを指定して実行するだけで、音声出力デバイスの切り替え処理は完了です。

これでメディアデバイスの切り替え処理が実装でき、デモのアプリケーションが完成です!

おわりに

簡単なデモという割に、説明がとても長くなってしまいました。
最終的なコード量だけ見れば多くは無いのですが、やはり Vue3 と WebRTC という技術を利用するために理解するべきことが多いということでしょうか。

Vue の世界はまだ Vue2 が多い印象ですが、これから Vue3 に切り替わっていくと思うので、これを機に Vue3 に触れてみていただければ幸いです。
特に Composition API は処理の再利用性を向上できるので、React の Function Component のように今後浸透していくのではないかと思います。
まだ採用されたケースが少なく参考を見つけるのが大変ですが、これから盛り上がっていってくれると嬉しく思います。

WebRTC については、リリースされてから結構時間が経っている技術ですが、まだまだ活発に開発が進んでいます。
ちなみに、今回はWebRTCのRTC部分(Real-Time Communication, P2P通信を使用する機能の部分)を使用していないため、膨大なWebRTCライブラリの始まりの部分しか触っていません。
(なのでWebRTCのサンプルと言って良いか正直微妙な所ではあります。。。)
すべてを覚えるのは難しい技術ですが、今回利用したように簡単に利用できるAPIが多く用意されているため、利用すること自体はそこまで大変ではないと感じます。
もしWebRTCに興味を持っていただけたら、自分でビデオ通話のアプリを作ってみるのも良いのではないでしょうか。

では長い記事でしたが、ここまでご覧いただきありがとうございました。

テコテック採用サイト

tecotec.co.jp