Hush Meet のアーキテクチャと設計について
┌─────────────────────────────────────────────────────┐
│ Chrome Extension (Manifest V3) │
│ │
│ ┌──────────────┐ ┌───────────────────────────┐ │
│ │ Popup UI │ │ Content Script │ │
│ │ (React) │ │ (meet.google.com) │ │
│ │ │ │ │ │
│ │ - Toggle │ │ ┌────────────────────┐ │ │
│ │ - Meter │◄───►│ │ Audio Analyser │ │ │
│ │ - Settings │ │ │ (Web Audio API) │ │ │
│ │ │ │ └────────┬───────────┘ │ │
│ └──────────────┘ │ │ │ │
│ ▲ │ ┌────────▼───────────┐ │ │
│ │ │ │ State Machine │ │ │
│ │ │ │ IDLE → MUTED → │ │ │
│ │ │ │ SPEAKING → GRACE │ │ │
│ │ │ └────────┬───────────┘ │ │
│ │ │ │ │ │
│ │ │ ┌────────▼───────────┐ │ │
│ │ │ │ Mute Controller │ │ │
│ │ │ │ (DOM操作)│ │ │
│ │ │ └────────────────────┘ │ │
│ │ └───────────────────────────┘ │
│ │ │
│ └─── Chrome Storage API (設定/状態の同期)─┘
│ │
└─────────────────────────────────────────────────────┘
src/
├── constants.ts # 定数・モード・閾値・ストレージキー定義
├── messages.ts # i18n メッセージ(EN/JA)
├── i18n.ts # ロケール管理
├── manifest.ts # Chrome拡張マニフェスト定義
├── background/
│ └── service-worker.ts # Service Worker(ゲーム起動等)
├── content/
│ └── index.ts # Content Script(状態マシン・モード制御・ショートカット)
└── popup/
├── index.html # Popup エントリHTML
├── main.tsx # React マウント
├── Popup.tsx # Popup UIコンポーネント
├── popup.css # スタイル
├── Equalizer.tsx # スペクトラムアナライザー
├── ThemeSwitcher.tsx # テーマ切替
├── LocaleSwitcher.tsx# 言語切替
└── GameLauncher.tsx # ミニゲーム起動
src/content/index.ts)Google Meet のページに注入されるスクリプトです。主に以下の責務を持ちます:
Web Audio API の AnalyserNode を使用して、マイク入力の音量をリアルタイムに測定します。
getUserMedia() → AudioContext → MediaStreamSource → AnalyserNode
│
getFloatTimeDomainData()
│
RMS (二乗平均平方根) 算出
設定パラメータ:
| パラメータ | 値 | 説明 |
|---|---|---|
fftSize |
512 | FFTウィンドウサイズ |
smoothingTimeConstant |
0.3 | 時間方向の平滑化係数 |
echoCancellation |
true | エコーキャンセルを有効化 |
noiseSuppression |
false | ノイズ抑制はオフ(VAD精度のため) |
5つの状態を持つ有限状態マシンで発話状態を管理します。
IDLE ──(Off 以外のモード選択)──► MUTED ──(音量 > speechThreshold)──► UNMUTING ──► SPEAKING
▲ │
│ (音量 < silenceThreshold)
│ │
└──(graceTimer満了)── GRACE ◄────────────────────┘
│
(音量 > silenceThreshold)
│
▼
SPEAKING
※ ユーザーが手動で Meet のマイクを触った場合も、モードは変えず状態だけ追従します。
| 状態 | 説明 | Meet操作 |
|---|---|---|
| IDLE | Off モードまたは未初期化 | なし |
| MUTED | 無音 → ミュート中 | ミュート |
| UNMUTING | 音声検出 → 解除中 | ミュート解除 |
| SPEAKING | 発話中 | なし |
| GRACE | 発話終了 → 猶予期間 | なし |
4つのモードで状態遷移の挙動を切り替えられます。Off が制御停止モードです。
| モード | ミュート解除 | ミュート | ショートカット |
|---|---|---|---|
| Off | なし | なし | 無効 |
| Auto | 音声検知で自動 | 無音で自動 | トグル |
| Auto-Off | 手動/ショートカット | 無音で自動 | トグル |
| Push-to-Talk | キー押下中のみ | キー離す→Grace→ミュート | ホールド |
カスタマイズ可能なキーボードショートカット(デフォルト: Ctrl+Shift+M)を提供します。
ショートカットキーはポップアップの設定から自由に変更できます。
ミュート解除とミュートで異なる閾値を使用することで、チャタリング(頻繁な切り替わり)を防止しています。
speechThreshold (発話開始) = 0.025 (デフォルト)
silenceThreshold (無音判定) = speechThreshold × 0.5 = 0.0125
音量
▲
│ ┄┄┄┄┄┄┄┄┄ speechThreshold (0.025) ┄┄┄┄ ← これを超えたらミュート解除
│
│ ┄┄┄┄┄┄┄┄┄ silenceThreshold (0.0125) ┄┄ ← これを下回ったら猶予開始
│
└──────────────────────────────────────────► 時間
Google Meet のミュートボタンを複数の戦略で検出します:
aria-label 属性による検索(日本語/英語対応)data-tooltip 属性による検索Ctrl+D キーボードショートカットのシミュレーション
ミュート状態の判定は data-is-muted 属性または aria-label の文言から行います。
src/popup/)React 19 で構築された設定UIです。Chrome Storage API を介して Content Script とリアルタイムに状態を同期します。
speechThreshold の調整(0.005–0.25)— モード別に保存
gracePeriod の調整(500ms–4000ms)— モード別に保存
Popup → Chrome Storage → Content Script
│ │
│ hushMeetConfig: object │ (感度/猶予時間の設定)
│ hushMeetMode: string │ (動作モード)
│ hushMeetShortcutKey: str │ (ショートカットキー)
│ │
│ ▼
│ Content Script → Chrome Storage → Popup
│ │
│ │ hushMeetState: string (現在の状態)
│ │ hushMeetLevel: number (音量レベル)
│ │ hushMeetSpectrum: array (スペクトラム)
└────────────────────────────┘
Popup と Content Script は直接通信せず、Chrome Storage API の変更イベントを介して双方向に通信します。
| ツール | バージョン | 用途 |
|---|---|---|
| Vite+ | toolchain | 依存管理・ビルド・テスト・検証 |
| Vite | 8.x | 内部ビルド基盤 |
| CRXJS Vite Plugin | 2.x | Chrome拡張のHMR・manifest生成 |
| React | 19.x | Popup UI |
| TypeScript | 5.9.x | 型安全性 |
| コマンド | 説明 |
|---|---|
vp install |
依存関係のインストール |
vp build |
プロダクションビルド |
vp test |
テスト実行 |
vp check |
format / lint / typecheck |