🎙️

ビデオ会議でカメラの映像の代わりに絵文字を配信するためのツールを作った

2021/10/06に公開
2

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

Zoomでの表示イメージ
自分のカメラ映像の代わりにこういうやつを表示

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

自分の映像の代わりに絵文字を表示

👆 画面共有ではなく、本来自分の映像が表示されるスペースに、絵文字を表示させるような形で使います。

https://live.catnose.me/

ユーザー登録なしで使えますが、ZoomやGoogle Meetにブラウザの画面を表示するためにOBSをインストールする必要があります。初回の設定手順は使い方ページで詳しく説明してあります。

作った理由

Zoomのイベントでパネルディスカッションに参加することになったからです(DevIO 2021)。顔は出したくないものの、自分だけ静止画のアイコンなのは違和感があると思い「絵文字でも表示しておくかー」と考えたのがきっかけです。

悩んだ結果、誰かが喋っているときに「🤣」「なるほど」「すごい」「気になる」などと合いの手を入れられるといいのでは?ということで作り始めました(Slackの絵文字からインスピレーションを得ていたのかも?)。

出来たものを実際に触ってみて「もしかすると自分以外にも需要があるかも?」という気がしてきたのでサービス化してみました。

仕組み

ブラウザ上で動くWebサービスです。[ルームを作る]ボタンを押すと、以下の2つのページが生成されます。

  1. 配信ページ
  2. 操作ページ

1の配信ページがZoom映るように設定し、2の操作ページからから配信ページに表示される内容を操作するという感じです。

操作ページのイメージ操作ページ

配信ページをZoomに表示するための設定さえ済ませれば、あとは操作ページのみを触ることになります。操作ページで表示する絵文字やテキストを選択して[適用する]ボタンを押すと、配信ページに表示が反映されます。
ページ間の表示内容の同期はFirestoreを使うことで簡単に実現できました(詳しくは後ほど)。

仮想カメラ部分はOBSなどのツールに任せることに

ブラウザに表示されている画面をZoomの画面共有で映すのは簡単ですが、自分のカメラの映像の代わりに表示するのは結構厄介です。

最初は仮想カメラ機能を持つMacアプリを開発しようと思ったですが、あまりにも大変そうだったので、このサービスではブラウザ上での表示のみを行うことにしました。

ブラウザに表示されている内容をOBSなどの外部ツールを使って仮想カメラとして出力し、それをZoomのカメラ設定から選択することになります。

初回の設定がすこし面倒になるぶん、少しでも設定がスムーズにできるように使い方の説明は詳しく載せることにしました。

使い方の説明ページ使い方の説明ページ

こだわりポイント

表示する絵文字を自由に選べる

絵文字を自由に選べる

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

テキストは選択式 + 自由入力可能に

テキストは選択式 + 自由入力可能に

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

アニメーションをかけられる

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

背景色を変えられる

背景色を変えられる

絵文字やテキストと同じように、使い勝手が良さそうな配色をあらかじめ選択肢として表示するようにしました。自由入力で配色を変えることもできます。

入力候補は自由に編集できる

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

よく使う組み合わせをプリセットとして保存しておける

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

その他

その他にも配信者のアイコンを画面の左上でぐるぐる回転させることができる機能などを用意しました。

技術的な話

ここからは技術的な話をしていきます。

使用したフレームワーク/ライブラリなど

  • Firebase(Firestoreのみ)
  • React
  • React Router
  • TypeScript
  • styled-components (CSS)
  • twemoji (絵文字をTwemojiの画像に変換)
  • emoji-mart (絵文字ピッカー)
  • @monaco-editor/react(JSONエディター)

create-react-appを使ったSPAの構成にしました。

デプロイ先

無料の範囲で十分に使えることや、表示速度などの理由からCloudflare Pagesにデプロイすることにしました。Cloudflare Pagesについてはこちらが参考になるかもしれません 👇
https://zenn.dev/catnose99/scraps/6780379210136f

ユーザー登録なしで使えるように

以下の理由から(とりあえず)ユーザー登録なしで使えるサービスしました。

  • 個人情報を持ちたくない
  • 利用規約をちゃちゃっと作りたい
  • ユーザー登録してまで使うほどのサービスなのか?と思ってしまった

Zoomで配信するページや、操作ページにはユニークなURLが発行されます。そのURLをLocal Storageに保存しておき、再度サービスに訪れてもらったときには同じページにリダイレクトするという仕組みです。

当然のことですが、ユーザー登録なしのサービスは心配事が少なくて楽ですね。

Firestoreの使用

このサービスのコアな機能はFirestoreによって支えられています。Firestoreが無かったら(色々と実装が面倒で)サービスを作ろうとしなかったと思います。

データの永続化

ユーザーが[ルームを作成する]ボタンを押すと、Firestoreにroomドキュメントが作られます。ドキュメントのIDにはランダムな値が自動で付与されるため、これをそのまま配信URLに使っています。

👇 イメージ的にはこんな感じ。

import { collection, addDoc } from "firebase/firestore"; // v9
// ルームを作成
const docRef = await addDoc(collection(db, "rooms"), 初期データ);
// このルームIDをlocalStorageに保存する
const roomId = docRef.id; 

このroomドキュメントが「どの絵文字を表示するか」「どのアニメーションをかけるか」などの情報を持っています。操作ページでやっていることはこのドキュメントのデータを更新(上書き)しているだけです。

Firestoreならページ間の同期が簡単

FirestoreではonSnapshot()メソッドを使うことでドキュメントの変更をリッスンすることができます。

https://firebase.google.com/docs/firestore/query-data/listen

この機能を使えば

  • 操作ページからroomドキュメントを変更(表示する絵文字や背景色を変更)
  • 配信ページで変更を検知し、リアルタイムで表示を更新

ということが簡単に実現できるというわけです。

セキュリティルールで一覧の取得をできないように

ユーザー登録がないため、操作ページのURLを知っている人は誰でも配信ページを操作できます。URLが載ったスクショをうっかりツイートしてしまったりすると、他の人に配信内容を操作される可能性が出てきます。そのため「漏れては困る情報は表示しないでね」「URLが漏れてしまったときは諦めてルームを作り直してね」というゆるい方針にしています。

なお、Firestoreのドキュメントの一覧を取得できないように、セキュリティルールでlistを禁止し、get(個別取得)のみを許可しています。

Firestoreのrule
// ...(省略)
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と使い方ページの作成、利用規約の作成のあたりです。久しぶりに個人開発したのですが、やっぱり楽しいですね。


というわけで、ビデオ会議で顔出ししたくない方はぜひ使ってみてください!

https://live.catnose.me

Discussion

Hidden comment
hama24hama24

「ルームを作る」というのが唐突で惑いました。日本人が分かりやすい感覚だと「放送室」でしょうか・・