7 min read

社内専用オンラインミーティングツールを Claude Code で作ってる

自社製品の WebRTC SFU を利用した社内専用のプライベートオンラインミーティングツールを Claude Code で作ってる。プロダクト名は Misora (み空色) 。

全くコードは書いていないし、見てない。CLAUDE.md をしっかり書いて、指示は複雑なタスクになりそうな場合は、かなり詳細に指示してる。

モチベーション

もともと自社専用のオンラインミーティングツールが欲しかったが、開発ツールを利用で困っていたわけではなかった。

またコストかけて作るメリットがあまり見えなかった。ただ Claude Code であれば、かなりコストを抑えて作れると判断してチャレンジしてみた。実際 1 日で最低限の機能を搭載したミーティングツールが出来た。

前提

  • VS Code の Claude Code 拡張を利用
  • Claude Opus 4 (/model opus) を利用
  • WebRTC SFU Sora を利用
  • GitHub Pages Private を利用
    • そのため静的サイト前提

利用技術

vite build でビルドして dist 以下を GitHub Pages Private にデプロイするという戦略。これにより認証の仕組みをすっ飛ばせる。静的サイトで作る事で運用コストを一気に下げる。

将来的には永続化情報を持たせたいとは思うが、そもそも社内利用しか想定していないので、現時点では静的サイトで十分。

機能

とにかく、自分たちが使いたい欲しい機能だけ実装していってる。

実装済み (追記で対応した機能含む)

  • ミーティング URL 生成
    • ただのルーム ID を乱数で生成するだけ
    • そもそも GitHub Pages Private で守られてる
  • 待機画面
  • 画面共有
    • Sora の同一クライアント ID 後勝ち機能を利用
  • スピーカーインジケーター
    • 話してる人に枠が出るやつ
    • Sora のスポットライト機能を利用
  • チャット
    • Sora のリアルタイムメッセージング機能を利用
  • 挙手機能
    • Sora のリアルタイムメッセージング機能を利用
  • マイク/カメラミュート
  • マイク/カメラデバイス選択
  • カメラ解像度変更
  • 参加者一覧
    • Sora のシグナリング通知機能を利用
  • アイコン設定機能
    • OPFS を利用
  • アイコン配布機能
    • Sora のシグナリング通知機能を利用
  • 画質指定
  • 音量メーター
  • チャット機能オン/オフ
  • ブラウザ更新してもチャットが消えないようにする
    • sessionStorage を利用
  • ブラウザ更新しても利用しているマイク/カメラが変わらないようにする
    • sessionStorage を利用

実装予定

スクリーンショット

WebRTC SFU Sora のスポットライト機能を利用することでクライアント側の負荷を下げている。これはアクティブスピーカーでなければ画質を下げて配信するという仕組み。

Misora いい名前
仮想背景やノイズリダクション、マイク/スピーカーテストなどを付けたい
アクティブスピーカーもっと太くしてもいいかも
3 人
ターミナルを画面共有
挙手機能思った以上に便利
参加者一覧、アイコンの自動生成とかアイコンを OPFS に入れておくとかやりたい
チャット、あまり使われなさそうではある

開発コスト(時間)

合計って書いてあるのは指示をしつつかかった時間、合計が書いてなければ一発でできた。

  • 画面共有
    • スゴイ大変だった
    • 合計で 3 時間くらいかかった
  • チャット
    • うち合わせに行く前に依頼して、うち合わせが終わったらできてた
    • 10 分
  • 挙手
    • 5 分
  • 準備室
    • 5 分
  • 参加者一覧
    • 5 分
  • 3 人以上のレイアウト
    • 合計で 30 分

フロー

まずは HTML + CSS (Tailwind CDN 版) + JavaScript による動くモックをかなりの数生成させた。レイアウトや構成、挙動などなど。これがかなり効果がある。とにかく無限リテイクできるし、HTML 1 枚なので確認も楽。

次に生成させた HTML を利用して Vite + React Router + React + Zustand + Sora JavaScript SDK に切り替えてもらった。10 分程度でできてきた。

その際 /add-dir で Sora JavaScript SDK のソースを読み込ませて、SDK の利用をできるかぎり間違えさせないようにした。実際、 SDK の利用自体は一切間違わなかった。

雑感

欲しかった会議システムがまだまだ課題はあるが、1-2 日でできてしまった。それも静的サイトなので運用コストも低い。

今後も積極的に改善して自社での利用が落ち着いてきたところで OSS (Apache-2.0) として公開し、Sora を利用して開発する際に /add-dir で Misora を追加するというのをやってもらいたいなと考えている。

VS Code + Claude Code + Claude Opus 4 + Claude Max Plan ($200) とても良いのでオススメ。

追記

2025-06-21

アイコンを Origin Private File System (以降 OPFS) にアップロードしたり、自分のアイコンを参加者に配布したりする機能を追加した。アイコンがあるのとないのでは全然違うのがよくわかった。

自分のアイコンを共有する仕組みも WebRTC SFU Sora のリアルタイムメッセージングの仕組みで追加できた。

ここまでのコード量。2 日感でこんな感じ。

アイコン加工機能はライブラリ依存なしでできるなら追加したいところ。

2025-06-23

アイコン編集機能を追加した。「無駄に高性能なアイコン編集機能をモーダルで作って」と依頼したらできた。5 分もかかってない。

ちなみに依存ライブラリは 0 で Canvas API のみ利用、WebP で出力してる。

ビットレートを指定できるようにした。コーデックは H.265 を採用している。5 分かかってない。

音量メーターを表示するようにした。結構たいへんで 20 分くらいかかった。

OPFS を利用してブラウザを更新したとしても、名前やアイコンが消えないようにした。

Slack のハドルの代わりに使おうと思ってるので、チャット機能はオフにできるようにした。環境変数に VITE_CHAT を用意して false の場合はチャット機能をオフにできる。

退室時にカメラがオフになるような処理を追加、凄く大事。

URL 生成部分に Sora のシグナリング URL とサービスのシークレットキーを指定できるようにした。これも OPFS で保存できるようにした。

ここまでのコード量。3 日感でこんな感じ。

2025-06-24

アイコン共有機能を追加。これは Sora のシグナリング通知機能を利用している。参加した時に他の人に設定した値を通知する機能。icon データを base64 でエンコードして共有している。

サイズをできるだけ小さくするためアイコンエディターでの圧縮率を上げて、一定以上のサイズの場合は保存できなくした。

Playwright を利用した E2E テストを追加し始めた。静的サイトということもあり、テストが想像以上に書きやすいようで URL 生成ページ、準備ページ、ミーティングページとテストを追加。

またミュート機能がすぐにデグレするので、ちゃんとミュートが動いているかの動作を確認する E2E テストを追加した。

基本的にデグレが相当発生するので E2E テストを積極的に書かせるのが大事ということがわかった。また E2E テストでログを出させると問題解決までの距離も短くなった。

ここまでのコード量 4 日目でこんな感じ。

非同期+ステートフルはかなり苦手っぽい感じなので、想像以上に上手くいかないのがわかってきた。

2025-06-25

準備ページ経由でしかミーティングページにアクセスできないようにした。これは SessionStorageを利用した一時トークンを使う仕組み。静的サイトで簡易リダイレクトを実現した。

URL を整理して、シンプルにした。

  • / で URL 生成ページ
  • /:roomId/preparation で準備ページ
  • /:roomId でミーティングページ
  • /exit で退出ページ

リダイレクト機能と合わせてミーティングページの URL を共有するだけで、準備ページに飛ぶようになり、シンプルになった。

ページのコンポーネント化を行った。最初はページにまるっと実装し、その後細かくコンポーネント化するのが効率良さそう。

E2E テストを増やし、GitHub Actions で Playwright の CI を回すようにした。H.265 を利用前提としているため Self hosted runner を利用して、macOS 上で回している。

SignalingUrl / SecretKey の設定画面を React/Tailwind 化した。OPFS 対応などはこれから。

ここまでのコード量 5 日目でこんな感じ。

接続エラーをわかりやすくしたり、SignalingUrl / SecretKey を設定して使えるようにしたい。

2025-06-27

社内で使い始めてみて、色々フィードバックを貰ったりしている。

名前の左にアイコンを出すようにした

細かいバグを取りつつ E2E テストを追加してる。さらにコンポーネント分割を行い、コンポーネントテストも追加している。

WebRTC ありき、ということもありコンポーネントテストは Playwright の @playwright/experimental-ct-react を採用した。ずっと experimental なのだけ気になるが、特に困らずテストできている。色々悩んだがコンポーネントと同じ場所に *.ct.tsx という感じでファイルを置くことにした。

静的サイトなので揮発性情報をできるだけ sessionStorage を使ってブラウザを更新しただけではなくならないようにする仕組みを追加した。

  • チャットを sessionStorage に保存し、まちがってブラウザを更新したときで過去のチャットは見えるようにする
  • マイクやカメラのデバイスが、間違ってブラウザを更新したとき切り替わらないように、sessionStorage に利用しているデバイス ID を覚えておき、更新後もそのデバイスを利用するようにする

とにかく静的サイトデプロイとWebRTC SFU Sora だけで実現できるようにするため、体験を悪くならないようにということで進めている。

ここまでのコード量 6 日目でこんな感じ。

前のタスクであった SignalingUrl / Secretkey を設定出来るようにしたい。

また、まだチャット機能のオン/オフだけしか対応していないが。いろいろな機能を環境変数で設定できるようにしたい。いらない機能を削除できることは大事。

Claude Code 作っているととにかくデグレするので E2E テストとコンポーネントテストでがっちり対策をしていきたい。