ビデオ会議で顔出しNGな人でも感情を表現できるように、絵文字やテキストをカメラ映像代わりに表示するためのサービスを作りました。

自分のカメラ映像の代わりにこういうやつを表示
👆 こんな感じでZoomやGoogle Meetでのビデオ会議で、自分の顔の代わりに絵文字を表示できるサービスです。絵文字に動きをつけたり、自由に文字入力することもできます。

👆 画面共有ではなく、本来自分の映像が表示されるスペースに、絵文字を表示させるような形で使います。
ユーザー登録なしで使えますが、ZoomやGoogle Meetにブラウザの画面を表示するためにOBSをインストールする必要があります。初回の設定手順は使い方ページで詳しく説明してあります。
Zoomのイベントでパネルディスカッションに参加することになったからです(DevIO 2021)。顔は出したくないものの、自分だけ静止画のアイコンなのは違和感があると思い「絵文字でも表示しておくかー」と考えたのがきっかけです。
悩んだ結果、誰かが喋っているときに「🤣」「なるほど」「すごい」「気になる」などと合いの手を入れられるといいのでは?ということで作り始めました(Slackの絵文字からインスピレーションを得ていたのかも?)。
出来たものを実際に触ってみて「もしかすると自分以外にも需要があるかも?」という気がしてきたのでサービス化してみました。
ブラウザ上で動くWebサービスです。[ルームを作る]ボタンを押すと、以下の2つのページが生成されます。
1の配信ページがZoom映るように設定し、2の操作ページからから配信ページに表示される内容を操作するという感じです。
操作ページ
配信ページをZoomに表示するための設定さえ済ませれば、あとは操作ページのみを触ることになります。操作ページで表示する絵文字やテキストを選択して[適用する]ボタンを押すと、配信ページに表示が反映されます。
ページ間の表示内容の同期はFirestoreを使うことで簡単に実現できました(詳しくは後ほど)。
ブラウザに表示されている画面をZoomの画面共有で映すのは簡単ですが、自分のカメラの映像の代わりに表示するのは結構厄介です。
最初は仮想カメラ機能を持つMacアプリを開発しようと思ったですが、あまりにも大変そうだったので、このサービスではブラウザ上での表示のみを行うことにしました。
ブラウザに表示されている内容をOBSなどの外部ツールを使って仮想カメラとして出力し、それをZoomのカメラ設定から選択することになります。
初回の設定がすこし面倒になるぶん、少しでも設定がスムーズにできるように使い方の説明は詳しく載せることにしました。
使い方の説明ページ

絵文字はAppleやWindowsなどの著作権に触れることがないように念のためオープンソースのTwemojiに変換するようにしました。

少しでもスムーズに自由入力ができるように、ショートカットキーCtrl + Iで入力欄にフォーカスするようになっています。

絵文字やテキストに使えるアニメーションをいくつか用意しました。

絵文字やテキストと同じように、使い勝手が良さそうな配色をあらかじめ選択肢として表示するようにしました。自由入力で配色を変えることもできます。
選択肢として表示される絵文字やテキスト、配色は設定ページで自由に変更できます。ここは実装の手間を省くため + コピペがしやすいように単純なJSONのエディターにしました。


例えば「赤い背景で、爆弾 💣 がブルブル震える」という表示を多用するという場合にはプリセットとして保存できます。プリセットは1クリックですぐに呼び出せます。

その他にも配信者のアイコンを画面の左上でぐるぐる回転させることができる機能などを用意しました。
ここからは技術的な話をしていきます。
create-react-appを使ったSPAの構成にしました。
無料の範囲で十分に使えることや、表示速度などの理由からCloudflare Pagesにデプロイすることにしました。Cloudflare Pagesについてはこちらが参考になるかもしれません 👇
https://zenn.dev/catnose99/scraps/6780379210136f
以下の理由から(とりあえず)ユーザー登録なしで使えるサービスしました。
Zoomで配信するページや、操作ページにはユニークなURLが発行されます。そのURLをLocal Storageに保存しておき、再度サービスに訪れてもらったときには同じページにリダイレクトするという仕組みです。
当然のことですが、ユーザー登録なしのサービスは心配事が少なくて楽ですね。
このサービスのコアな機能はFirestoreによって支えられています。Firestoreが無かったら(色々と実装が面倒で)サービスを作ろうとしなかったと思います。
ユーザーが[ルームを作成する]ボタンを押すと、Firestoreにroomドキュメントが作られます。ドキュメントのIDにはランダムな値が自動で付与されるため、これをそのまま配信URLに使っています。
👇 イメージ的にはこんな感じ。
import{ collection, addDoc}from"firebase/firestore";// v9// ルームを作成const docRef=awaitaddDoc(collection(db,"rooms"), 初期データ);// このルームIDをlocalStorageに保存するconst roomId= docRef.id;このroomドキュメントが「どの絵文字を表示するか」「どのアニメーションをかけるか」などの情報を持っています。操作ページでやっていることはこのドキュメントのデータを更新(上書き)しているだけです。
FirestoreではonSnapshot()メソッドを使うことでドキュメントの変更をリッスンすることができます。
https://firebase.google.com/docs/firestore/query-data/listen
この機能を使えば
roomドキュメントを変更(表示する絵文字や背景色を変更)ということが簡単に実現できるというわけです。
ユーザー登録がないため、操作ページのURLを知っている人は誰でも配信ページを操作できます。URLが載ったスクショをうっかりツイートしてしまったりすると、他の人に配信内容を操作される可能性が出てきます。そのため「漏れては困る情報は表示しないでね」「URLが漏れてしまったときは諦めてルームを作り直してね」というゆるい方針にしています。
なお、Firestoreのドキュメントの一覧を取得できないように、セキュリティルールでlistを禁止し、get(個別取得)のみを許可しています。
// ...(省略)match/rooms/{roomId}{ allow get;// 他のユーザーが作ったroomIdを推測できないようlistは許可しない allow create; allow update;}一つややこしい点があったとすれば、テキストを相対的なサイズで表示するという点です。Zoomに映す配信ページと、操作ページのプレビューとでは、表示サイズ(枠の大きさ)が異なります。

そのため、枠の大きさに応じて絵文字やテキストのサイズを変える必要があります。絵文字についてはTwemoji変換時に<img />要素に変換されているため、幅を相対的に指定する(width: ○○%)だけで済みます。
一方で、テキストのサイズについてはそのような指定が困難です(font-size: ○%では思ったような幅にはならない)。
最初は親要素の幅からfont-sizeを計算するようにしてみたのですが、一瞬ガタツキが発生することや、ブラウザによって最小のfont-sizeが決められていることなどから断念。最終的にテキストはcanvasに描画することにしました。
canvasであれば、width: 100%とするだけで親要素の幅に応じてレスポンシブに表示されるため、配信ページとプレビューの差異を無くすことができました。
Firestoreの使用量だけです。ドキュメントが1種類だけなので、相当使われても月数十円〜数百円で済む見込みです。
たぶん合計で15時間くらいかかったと思います。いちばん時間がかかったのはLPと使い方ページの作成、利用規約の作成のあたりです。久しぶりに個人開発したのですが、やっぱり楽しいですね。
というわけで、ビデオ会議で顔出ししたくない方はぜひ使ってみてください!
バッジを受け取った著者にはZennから現金やAmazonギフトカードが還元されます。