2Dバーチャルオフィス(近接音声・AI議事録)
アバターで近づくだけで会話が始まる。音声・ビデオ・画面共有・AI議事録を、ひとつの2Dオフィスで。

Overview
プロジェクト概要
アバターでオフィスに「出社」し、ほかのメンバーに近づくと自動で音声がつながる、2Dバーチャルオフィスを開発しました。会議室に入れば、その場でビデオ通話と画面共有ができます。さらに、AIが会議を文字起こしして要約し、議事録にまとめます。対象は、リモート勤務・ハイブリッド勤務のスモールチーム(2〜20名)です。
同種のサービスでは、一定の距離で通話がオンとオフに切り替わる方式が使われています。本サービスでは、距離に応じて音量がなめらかに変わり、相手のいる方向から声が聞こえる方式に作り直しました。開発期間は約6か月で、要件定義から本番運用までの全工程を、当社のエンジニア1名で担当しました。本番リリース後は、導入150チーム・登録ユーザー約900名の規模で稼働しています。
※ お客様との守秘義務のため、お客様名・製品名は記載していません。掲載している画面とデータは、説明用に再構成したイメージです。
- 分類
- 受託開発。リアルタイムの音声・ビデオ通話とAI議事録を備えたWebサービスの開発
- 開発期間
- 約6か月
- 体制
- エンジニア1名(要件定義から本番運用まで全工程)
- 規模
- 導入150チーム / 登録ユーザー約900名 / ピーク時の同時接続 約300
- 利用者
- リモート勤務・ハイブリッド勤務のスモールチーム(2〜20名)
- 業種
- IT・Webサービス
- ロール
- 要件定義 / 技術選定 / アーキテクチャ設計 / DB設計 / バックエンド / リアルタイム基盤 / メディア(WebRTC) / フロントエンド / AI機能 / AWSインフラ / CI/CD / 監視 / 本番運用
- 技術
- Ruby on Rails / AnyCable / LiveKit(WebRTC) / Phaser 3 / TypeScript / PostgreSQL / Redis / Claude API / Amazon Transcribe / AWS(ECS Fargate / EC2) / Terraform

01 — 導入前の課題
同じチームなのに、隣に誰もいない
リモート勤務やハイブリッド勤務が広がり、チームは「同じ空間にいる感覚」を失いがちになりました。自宅やコワーキングスペースなど別々の場所で働いていると、「今、少し話せますか?」と気軽に声をかけにくくなります。少し話すだけのために会議のURLを発行し、ツールを行き来する必要があります。席を外している間に何が決まったのかも、つかみにくくなります。本サービスは、対象をスモールチームに絞り、こうした日々の不便を解消することをねらいました。
- 雑談が減り、孤立感が生まれる
- 少し話すために、会議のURLを発行する
- 複数のツールを行き来する
- 席を外した間の出来事をつかみにくい

02 — 近接音声
話しかけるボタンも、会議URLもいらない
オフィスのマップ上で、アバターを相手に近づけるだけで音声がつながり、離れると自然に切れます。音量は、相手との距離に応じてなめらかに変わります。画像の例では、近くにいる山田さんの声は100%、少し離れた鈴木さんの声は35%で聞こえています。相手のいる方向から声が聞こえるように、左右の定位も付けました。アバターには、在席・離席・会議中・集中といった状態が色で表示されます。アバターの位置は、1秒間に10〜15回、差分だけを配信し、受け取った側で補間してなめらかに描いています。
- 近づくだけで音声がつながり、離れると切れる
- 距離に応じて、音量がなめらかに変化
- 相手のいる方向から声が聞こえる
- 在席・会議中・集中などの状態を色で表示
開発中の課題と解決
近接音声を自然にする(最も難しかった点)
- 課題
- 一定の距離でオンとオフが切り替わる方式では、会話が不自然でした。利用者どうしを直接つなぐ構成は、同時に話す人が増えると破綻しました。エコーやハウリング、モバイルでの音声の制約も壁になりました。
- 解決
- 音声をSFU(LiveKit)経由に寄せ、音声を受け取る相手は近くにいる人だけに絞って、通信量を抑えました。距離から音量を決める減衰のカーブは、実際に動かしながら調整しています。ある距離までは最大の音量、そこから先はなめらかに無音へ近づく形にしたことが、会話の自然さの決め手になりました。本番稼働後の音声の片道の遅延は200ms未満、音声の接続の確立は1.5秒未満です。

03 — 会議室とAI議事録
そのまま会議室へ。共有も議事録も、ここで完結
マップ上の会議室に入ると、その場でビデオ通話と画面共有が使えるようになります。外部の会議ツールへ移る必要はありません。会議室の中にいる人どうしは、距離に関係なく最大の音量で聞こえ、部屋を出ると自動で退出します。会議の音声はAmazon Transcribeで文字にし、Claude APIで要約して、要約・決定事項・対応事項・次回の予定をまとめた議事録にします。できあがった議事録は、チームのチャットへ自動で投稿されます。
- 会議室に入るだけで、ビデオ通話と画面共有を開始
- 外部の会議ツールやURLの発行が不要
- AIが会議を文字起こしし、要約して議事録に
- 議事録をチームのチャットへ自動で投稿
開発中の課題と解決
WebRTC・メディアの安定化
- 課題
- UDPを使うメディアの配信は、Fargateと相性が悪く、NAT越えやポートの開放でつまずきました。
- 解決
- LiveKitはEC2に分けて構築してUDPを確保し、TURNを併用しました。アプリの層(Fargate)とメディアの層(EC2)で役割を分け、Terraformで同じ構成を再現できるようにしています。本番稼働後の月次の稼働率は99.9%です。

04 — 不在中の要点
40分離れていても、40分を読み返さなくていい
席を外していた人には、不在だった間のチャットと議事録から、AIが「不在中の要点」を作って示します。画像の例では、10:00〜10:40の40分間の出来事が、「デザイン案はB案で進行」「山田さんが資料を更新」「15:00に再確認予定」の3件にまとめられています。席に戻ったとき、流れたチャットをさかのぼって読む必要がありません。AIの処理は重いため、ジョブとして裏側で実行しています。頻度の高いヘルプ応答と、要約・議事録とで、Claudeのモデルを使い分けました。
- 不在中のチャットと議事録から、要点だけを整理
- 何があったかを、数十秒で把握
- 用途に応じてClaudeのモデルを使い分け
- 重いAI処理は、ジョブとして裏側で実行
開発中の課題と解決
高頻度の位置同期をさばく
- 課題
- RailsとActionCableだけでは、多数の接続への高頻度の配信で並行性が足りませんでした。位置をデータベースに書くと、すぐに逼迫しました。
- 解決
- WebSocketの処理をAnyCable(Go)に任せ、Rubyでの開発のしやすさは保ちました。位置などの一時的な状態はRedisで持ち、差分を10〜15Hzで送って受け取った側で補間することで、送る量を減らしています。再接続のときは、スナップショットから復元します。本番稼働後、ピーク時の同時接続は約300、体感の移動の遅延は150ms未満です。
Scope
担当範囲
当社はエンジニア1名の体制で、要件定義から本番運用まで、すべての工程を担当しました。お客様とは要件と優先度をすり合わせながら、体験の中心となる部分から段階的に作りました。
| 工程 | 担当 |
|---|---|
| 要件と優先度のすり合わせ | お客様と当社 |
| 要件定義、技術選定 | 当社 |
| アーキテクチャ設計、DB設計 | 当社 |
| バックエンド(認証、チーム、課金、データの保存) | 当社 |
| リアルタイム基盤(位置の同期、在席状況、チャット) | 当社 |
| メディア(WebRTCによる音声・ビデオ・画面共有) | 当社 |
| フロントエンド(2Dオフィスの描画、管理・設定画面) | 当社 |
| AI機能(議事録、要約、不在中の要点) | 当社 |
| AWSインフラ、CI/CD、監視 | 当社 |
| 本番運用 | 当社 |
1名ですべての層を担当したため、「体験の中心を最短で成立させる」という判断と、リアルタイム通信・メディアという難しい部分の作り込みが、最も大きなテーマになりました。
Architecture
使用技術・システム構成
- 記録の中心
- Ruby on Rails 8 / PostgreSQL(Amazon RDS)。認証、チーム、課金、データの保存。会議の参加トークンもここで発行
- リアルタイム層
- AnyCable(Go) / Redis(Amazon ElastiCache)。在席状況、位置、チャット、通話の接続手続き。位置は差分を10〜15Hzで配信
- メディア層
- LiveKit(SFU)。音声、ビデオ、画面共有。ブラウザとはWebRTCで接続
- 音声の処理
- Web Audio。距離に応じた音量の調整、左右の定位
- フロントエンド
- TypeScript / Phaser 3(2Dオフィスの描画) / Hotwire・Tailwind CSS(管理・設定画面) / Vite
- AI・音声認識
- Claude API(議事録、要約、ヘルプ応答) / Amazon Transcribe(会議の音声の文字起こし)
- 認証・課金
- Devise / Pundit / Stripe(サブスクリプション)
- 非同期処理
- Sidekiq。重い処理をジョブとして実行
- 実行基盤
- AWS ECS Fargate(アプリ) / EC2(LiveKit) / ALB / S3 / CloudFront / SES
- 開発・運用
- Docker / Terraform / GitHub Actions / Sentry
主な技術の選定理由
Railsを記録の中心に
Railsが得意な認証、課金、データの保存に集中させました。苦手な高頻度の配信とメディアの配信は、専用の層に任せています。
AnyCable(Go)
RailsとActionCableだけでは、多数の接続への高頻度の配信で並行性が足りませんでした。WebSocketの処理をGo製のサーバーに任せ、チャネルの処理はRubyのまま保ちました。
LiveKit(SFU)
利用者どうしを直接つなぐ構成は、同時に話す人が増えると破綻しました。SFUを経由させ、接続の数が人数の2乗に比例して増える問題を避けています。
LiveKitをEC2に置く
UDPを使うメディアはFargateと相性が悪かったため、EC2に分けて構築し、UDPを確保しました。
位置をRedisで扱う
位置をデータベースに書くとすぐに逼迫したため、位置などの一時的な状態はRedisとメモリで扱いました。
Claudeのモデルの使い分け
頻度の高いヘルプ応答には軽量なモデル、要約と議事録には高性能なモデルを使い、コストと品質の両方を保ちました。
Process
開発の進め方
- 1
「まず、同じ空間にいる感覚を最短で成立させる」という方針で、フェーズを分けて進めました。約6か月の中で、近接音声の作り込みに最も時間を使っています。
- 2
Phase 0(基盤):土台となる基盤を作りました。
- 3
Phase 1(MVPコア):近接音声、会議、在席状況。近接音声を最優先で作り込みました。
- 4
Phase 2(AI差別化):AIによる議事録、要約などの機能を加えました。
- 5
Phase 3(拡張・本番化):機能の拡張と、本番運用への移行を行いました。本番リリースの後は、スモールチーム向けに段階的に展開しています。
- 6
インフラはTerraformでコードにし、同じ環境を再現できるようにしました。CI/CDにはGitHub Actionsを使っています。
Quality & Security
品質・セキュリティ・運用
認証・権限
ログインはDevise、権限はPunditで制御します。
会議への参加
会議の参加トークンはサーバー側で発行し、LiveKitへ渡します。
切断への備え
位置などの一時的な状態は、切断・再接続のときにスナップショットから復元します。
通信の確保
LiveKitをEC2に分けてUDPを確保し、TURNを併用してNAT越えに対応します。
インフラの再現性と監視
Terraformでインフラをコードにし、同じ構成を再現できます。CI/CDにGitHub Actions、監視にSentryを使っています。
稼働
本番リリースの後、段階的に展開し、月次の稼働率99.9%で安定して稼働しました。
Results
成果
近づくだけで自然に話せる近接音声と、AIによる議事録の自動作成を備えた2Dバーチャルオフィスを、エンジニア1名で要件定義から本番運用まで担当し、150チーム・約900名の規模で安定して稼働させました。
- 会議の議事録の作成を自動化しました。手作業で約15分/会議かかっていたものが、ほぼ0分になりました(約90%削減)。
- 近づくだけで話せるため、会議を設定しない気軽な会話が日常のものになりました。
- 通話、会議、画面共有がオフィスの中で完結し、外部の会議のURLを発行する必要がなくなりました。
- AIによる要約で、不在中に何があったかを数十秒でつかめるようになりました。
- 稼働の実績:導入150チーム、登録ユーザー約900名、ピーク時の同時接続 約300、月次の稼働率99.9%。
- 品質の実績:体感の移動の遅延150ms未満、音声の片道の遅延200ms未満、音声の接続の確立1.5秒未満、会議の最大ストリーム数は1室あたり約30。
FAQ
技術面のよくあるご質問
- Q. 近接音声は、ほかのサービスと何が違いますか?
- 一定の距離でオンとオフが切り替わる方式ではなく、相手との距離から音量を連続的に決め、左右に定位させています。ある距離までは最大の音量で、そこから先はなめらかに無音へ近づきます。
- Q. 人数が増えても通話は保てますか?
- 全員がSFU(LiveKit)へ音声を送り、各利用者は近くにいる相手の音声だけを受け取ります。利用者どうしを直接つなぐ構成で起きる、接続の数が急に増える問題を避けています。
- Q. なぜ構成を3層に分けたのですか?
- 性能の要件が異なる処理を1つにまとめないためです。Railsは認証・課金・データの保存に集中させ、高頻度の配信はAnyCable、メディアの配信はLiveKitに任せています。
- Q. アバターの位置はどう同期していますか?
- 10〜15Hzで差分だけを配信し、受け取った側で補間して描いています。位置はデータベースに書かず、Redisとメモリで扱います。切断したときは、スナップショットの再送で復元します。
- Q. AIの議事録はどのように作られますか?
- 会議の音声をAmazon Transcribeで文字にし、Claude APIで要約して議事録にまとめ、チームのチャットへ自動で投稿します。重い処理はSidekiqのジョブとして実行しています。
- Q. なぜLiveKitだけEC2で動かしているのですか?
- UDPを使うメディアはFargateと相性が悪かったためです。LiveKitはEC2に分けてUDPを確保し、TURNを併用してNAT越えに対応しています。
Tech Stack
