Firestoreを利用したWebRTCビデオ通話入門

4 min read読了の目安(約4100字

対象の読者

はじめに

FirebaseのFirestoreを使うことでフロントエンドのコードだけでWebRTC通信に必要な情報の交換(シグナリング)をすることができます。

ビデオ会議システムの需要が増え、低遅延でNATを超えてビデオ通話をするためにWebRTCが注目されてしばらく経ちました。
ブラウザでWebRTCビデオ通話を実現するためにSDKを使うのもいいですが、
将来サービスがスケールしたときのことを考えてがんばって自前で作ったほうが得かもしれません。
私は1年前にWebRTCを使った、リモートワークのためのコワーキングスペース「Air Commute」というサービスを作っていたところ、スタートアップに誘われてとあるビデオ通話システムを作ることになりました。

今回は1年前にCodelabのガイドの通りに実装しても動かず格闘した軌跡をたどりながら動作するところまで持っていきたいと思います。
(なお動くサンプルがsolutionブランチに作られたようです。結果だけ見たい人はそちらをどうぞ)

Codelabガイドの解説

完全なコードよりはまず取り合えず動くコードを目指してwebrtc.orgの下のほうのリンクにある

https://github.com/webrtc/FirebaseRTC/blob/master/public/app.js
の不完全な部分を埋めていきたいと思います。

webrtc.orgのガイドと見比べながら進めてください。

https://webrtc.org/getting-started/firebase-rtc-codelab

5.ローカルサーバーを実行します まで進めてください

Firebaseの準備も済み、ブラウザで http://localhost:5000 を開くとこのように表示されていると思います。
初期画面

getUserMediaでカメラを開くところまでのコードが用意されています

6.新しい部屋を作成する へ

※便宜的にルームを作成する人をOffer側、ルームに入る人をAnswer側と呼びます

public/app.js

PeerConnectionを作成しlocalStreamのtrackを追加した後の

// Add code for creating a room here

// Code for creating room above

の間にランダムなIDのルームを作成してFirestoreに作成したOfferを書き込むコードを追加します。(webrtc.orgのガイドを参照。以下同じ)
ここまではいいと思います。

次にガイドによると「次にデータベースへの変更をリッスンし」とあるように

// Listening for remote session description below

のところの解説が始まり、

// Code for creating a room below

// Code for creating a room above

はどうした?となります。ルームを作るコードはさっき書いたのでこのコメントは余計です。

気を取り直して「次にデータベースへの変更をリッスンし」の部分。

// Listening for remote session description below

// Listening for remote session description above

にコードを追加しアロー関数が->になっているのを=>に直します。
onSnapshotはroomRefの指すドキュメントが変化するたびに呼ばれ、相手からのAnswerを待ちます。そしてPeerConnectionにsetRemoteDescription(Answer)します。

7.部屋に参加する へ

かなり飛んでjoinRoomById()内すなわちAnswer側の

// Code for creating SDP answer below

// Code for creating SDP answer above

にコードを追加します。コピペされたルームIDのドキュメントを参照してOfferを取得し、Answerを作成して同じ場所に書き込みます。

8.ICE候補を収集します へ

ここまでICE candidates 関係の説明が後回しにされていましたがようやく登場します。
コードが関数の形をしているのでpublic/app.jsの下のほうに追加して、

// Code for collecting ICE candidates below

// Code for collecting ICE candidates above

の部分には呼び出しだけ書きます。2か所ありますが、Offer側とAnswer側でlocalNameとremoteNameを入れ替えることでお互いのICE Candidateを読んでPeerConnectionにセットすることができます。
solutionブランチに倣ってoffer側が書き込むICE Candidateを'callerCandidates'、Answer側を'calleeCandidates'としておきましょう。

collectIceCandidates(roomRef, peerConnection, 'callerCandidates', 'calleeCandidates')
collectIceCandidates(roomRef, peerConnection, 'calleeCandidates', 'callerCandidates')

Firestoreは構造的にドキュメントの下にさらにコレクションを作ることができるようになっており、サブコレクションといいます。
Firestoreの構造

Offer側からOfferとcallerCandidates内のICE CandidateがAnswer側へ、Answer側からAnswerとcalleeCandidates内のICE CandidateがOffer側へ渡されていることがわかれば良いです。

なお

// Listen for remote ICE candidates below

// Listening for remote ICE candidates above

の部分はcollectIceCandidates に含まれているのでやはり余計です。

これで動作するかブラウザを2つ開いて通信できるか試してみましょう。(カメラが2つ必要です。SnapCameraなど仮想カメラ含む)
片方のブラウザでOPEN CAMERA & MICROPHONEを開いてCREATE ROOMをクリック。ROOMのランダムなIDをコピー。
もう片方のブラウザで同じくOPEN CAMERA & MICROPHONEを開いてJOIM ROOMをクリック。IDをペーストしてJOIN。

お疲れ様です。動きません。

仕上げ

動くようにするために、ひとつはpeerConneciton になっているtypoをpeerConnection直します。

もうひとつcreateRoom()内の

    localStream.getTracks().forEach((track) => {
      peerConnection.addTrack(track, localStream)
    })

をOffer作成前に持ってきます。

無事動きました。

peerConnectionがtrackを受信するとRemoteStreamに追加してVideoタグに映像が表示されるようになっています。
Firebase hostingにデプロイすると、インターネット越しに他のブラウザとビデオ通話することができます。

firebase deploy --only hosting

取り合えず動くようになりましたが他に

  • Firestoreのセキュリティルール
  • onSnapshotのunsubscribe()
  • stateをみてSDPやICE Candidateをセットするか判断
  • candidateが空の時はaddIceCandidateしない
  • getUserMediaの引数を調整
    など完成形に近づけるにはやることが山ほどあります。

ここからルームIDを人力でコピペではなく送受信を自動化したり、接続の数だけルーム(?)を作成して部屋に複数人入れるようにしたりと発展させることができます。
開発フレームワークはFirestoreと相性のいいVueやReactなどをおすすめします。

宣伝

GoGoHouseというWebRTCを利用した音声トークサービスを個人開発で作っています。

https://gogo.house/
Twitter連携でフォロワーとゆるく音声トークをすることができるので遊んでみてください!

以上ご覧いただきありがとうございました。