他のタブを開いていてもショートカットで Google Meet のマイクをミュートできる Chrome 拡張を作った
Google Meet で picture-in-picture が提供されましたね[1]
他のタブの議事録とかを開きつつ参加者の反応を見たりと便利に使っていますが、そのままショートカットキーでマイクのミュート/ミュート解除ができたらさらに便利そうだと思い、Chrome 拡張を作ってみました
使い方
Chrome Web Store からインストールしてください
(インストール時既に開かれている Google Meet のタブはリロードが必要です)
以下のショートカットキーが利用可能です
※変更可能、手順は後述します
ショートカットキー | 用途 |
---|---|
Ctrl+Shift+D macOS では Option+Shift+D
|
マイクのミュート/ミュート解除 |
Ctrl+Shift+E macOS では Option+Shift+E
|
カメラのオンオフ |
ショートカットキーの変更方法
Chrome の拡張機能設定ページ (chrome://extensions
) に行き、画面左のメニューバーから キーボードショートカット
をクリックしてください
Google Meet - Global Shortcuts
の欄を探し、お好みのキーに変更してください
Toggle Microphone
がマイク、Toggle Camera
がカメラのオンオフです
実装
フレームワーク
勤務先の別事業部が直近 Chrome 拡張を開発/提供開始しており、WXT というブラウザ拡張のフレームワークが結構便利という話を雑談で聞いていたので採用しました
今回はバンドルするスクリプトも使用する API も多くない単純な拡張なので特に苦労することもなく終わってしまい評価が難しいですが、HMR 効いたりと開発者体験はなかなか良かったです
もっと実務的な Chrome 拡張を開発する上での苦労などに興味のある方は同僚の記事をどうぞ
Commands API
WebExtensions API にはショートカットキーを実装するための commands[2] という API があり、これを利用したショートカットには以下のような特徴があります
- ユーザーがキーバインドを自由にカスタムできる
- UI もブラウザが用意してくれるためお手軽
- ブラウザウィンドウがアクティブでさえあれば、アクティブなタブに関わらず効くショートカットを定義できる
- ページ内で実行される通常のスクリプトや content script で
keydown
イベントなどを利用して実装した場合は、当然ながらそのタブがアクティブである場合にしか効かない
- ページ内で実行される通常のスクリプトや content script で
今回は Meet のタブ以外を開いているときにも効くことが肝心であるため、Commands がうってつけでした
commands.onCommand.addListener
は content script からは利用できないため、まずは background script (service worker) で command event を受け取り、Meet のタブを探して tabs.sendMessage
で転送し、Meet のタブで動いている content script でそれを受け取る流れとしました
Content script でのページ上の操作
Content script で実際にマイクのミュートなどページ上の操作を行うには Element.querySelector
などでボタンを探して HTMLElement.click
する方法がありますが、Google の製品群はスタイルなどの指定にランダム生成されたクラス名が使われており、data-test-id
などもなく、操作すべき要素を安定して探索するのが難しいことが多いです
以下のように aria-label
を用いれば一応可能ではありますが、Google Meet は多言語対応のため表示言語が変わると aria-label
も変わってしまい、全言語分をクエリに列挙するのは大変です
document.querySelector('button[aria-label="Turn off microphone"]');
そこで今回は Google Meet がデフォルトで持っている Ctrl+D
/Ctrl+E
といったショートカットキーを活用し、ユーザーが Ctrl+D
をタイプした場合に発生する KeyboardEvent
を模擬的に発火させることにしました
dev tools で keydown
/ keypress
といったイベントの listeners を1つずつ消して検証したところ、当該ショートカットは document
で keydown
event を listen して実装されていることがわかったため、模擬的な発火の実装は以下のようになりました
// マイクのミュート/ミュート解除コマンドが流れてきた場合
document.dispatchEvent(
new KeyboardEvent("keydown", { ctrlKey: true, key: "d" }),
);
EventTarget.dispatchEvent
はこのようにブラウザ拡張の content scripts や userscripts などでページ上の操作を再現したい場合たまに役立つので、頭の片隅に置かれていると良さそうです
Discussion