A
あ
タブの中の 同時通訳
Interpretab という Chrome 拡張機能です。タブが再生している音声を聞き取って、声に出して通訳します。
逆に、自分の声を訳して相手に届けることもできます。あいだに第三者のサーバーは入りません。通信も API キーも、
ブラウザの中だけで完結します。まずはデモをご覧ください。
デモ: タブを通訳する
どんなタブでも
動画、ウェビナー、ポッドキャスト。元の言語は指定するのではなく、自動で判別する。
声で出る
通訳を音声で再生する。元の音声は音量を 15% まで下げて、後ろで鳴らしたまま。
字幕も出る
ページの上に 3 行のロール字幕。サイドパネルには全文が残る。
クリップは自動で流れる。まず字幕の行、次にパネルの全文、最後に中国語へ切り替わるところで言語メニューを指す。
デモ: 自分の声を通訳する
あなたの声
話しはじめた時点で拾い、文が終わる前から訳しはじめる。
双方向
自分の発言も相手の発言も、訳された状態で 1 つのパネルに並ぶ。
通話の中へ
Meet
のマイク一覧に出るので、選べば相手には訳した声が届く。
後半は双方向モード。1 つのパネルに 2 言語で、どちらの向きかは自分で判定している。
Interpretab のアーキテクチャ
Web Audio
ブラウザ内蔵の音声処理。取り込み、レート変換、
音量調整、再生をこれでやる
WebSocket
開いたままの接続。どちらの端からでも
好きなときに送れる
Gemini Live API
音声を入れると、話している文が終わる前に
訳した音声が返ってくる
Chrome · 1 つの offscreen document 。画面を持たない裏方のページで、音声処理もソケットもすべてここに置く
タブの音声
48 kHz、変換せずそのまま
マイク
話している自分の声
スピーカー、
ページ、通話へ
3 つの AudioContext
ctxPass · タブと同じレート
そのまま流す。音量は 15% に下げる
ctxUp · 16 kHz
録音 worklet 。32 ms ずつ送る
ctxDown · 24 kHz
再生 worklet。訳した音声を鳴らす
ソケット 1 · タブの音声
ソケット 2 · マイク
1 本で送受信する。行きが元の音声、帰りが訳した音声
送るのは ctxUp、受け取るのは ctxDown
Gemini Live API
generativelanguage.googleapis.com
第三者のサーバーを通らない
作者側にサーバーがないので、
キーを預かることもない
全体の構成です。主なコンポーネントは 2 つあります。Web Audio はブラウザ内蔵の音声処理 API で、
音の取り込みとサンプルレート変換をやります。
Gemini Live API は、音声を送ると、モデルからの応答を WebSocket を介してリアルタイムに返します。
アプリケーション全体は、offscreen document
という 1 枚の見えないページに入っています。 AudioContext はサンプルレートごとに 3 つ。 WebSocket は、タブの音声用と
マイク用のそれぞれに 1 本ずつ使います。
2 つの Live モデル
違いは、話している最中から訳すか、話し終わるのを待つか。タブの音声はつねに同時通訳で、マイクは利用者が選ぶ。
同時通訳
gemini-3.5-live-translate-preview
入力 $3.50 / 出力 $21
話している最中から訳す。ターンの終わりを待たない
元の言語はモデルが判別する。指定するのは訳す先だけ
70 以上の言語に対応。ただし 1 セッションで訳す先は 1 つ
モデルへの事前指示を受け付けない。用語集も渡せない
0.98 秒
発話の終わりから訳し終わりまで
92.4%
採点した 198 文のうち合格
双方向の会話
gemini-3.1-flash-live-preview
入力 $3 / 出力 $12
ターンが終わるのを待ってから訳す
使う 2 言語を先に伝える。どちらで話しても、もう一方へ訳す
モデルへの事前指示を受け付ける。用語集はここに乗せる
1.60 秒
発話の終わりから訳し終わりまで
99.3%
採点した 281 文のうち合格
どちらを使うにも、つなぎ方は同じ。ソケットを開いて setup フレーム を 1 つ送る。違うのは、その中身。
同時通訳
"generationConfig" : { ..., "translationConfig" : { "targetLanguageCode" : "ja" , "echoTargetLanguage" : false } }
双方向の会話
"systemInstruction" : { "parts" : [{ "text" : "Interpret between English and Japanese…" }] }
価格は音声トークン 100 万件あたりの米ドル。同時通訳を 1 分間流すと、入力と出力あわせて約 $0.037。レイテンシと合格率は 1 時間の soak で計測した実測値。
Interpretab では 2 つの Live API モデルを使います。同時通訳モデルは話している最中から
訳しはじめ、事前のインストラクションは受け付けません。 会話モデルのほうは
相手が話し終わるのを待ちます。そのぶん翻訳は遅くなりますが、品質が高く、コストも安くて、また用語登録機能を持たせられます。
Meet とどう組み合わさるか
拡張機能を OS のマイクとして登録することはできない。でも、ページの中でならできる。
次は、これを Meet につないで双方向の通訳にする話です。Chrome 拡張の音声出力を OS のマイクとして
登録することはできません。でも Meet のページの中でなら可能です。 Meet のスクリプトが動く実行環境に、こちらの
スクリプトを追加して、Interpretab という名前のマイクをデバイス一覧に足します。 利用者が Meet の設定画面で
それを選ぶと、翻訳した音声が相手に届きます。 ただし、同じ 1 枚のページでも Meet 側と拡張機能側は別々の実行環境で動いていて、
拡張機能側からマイクの一覧を書き換えても Meet からは見えません。そこで両側にスクリプトを 1 つずつ置いて、
postMessage で橋渡しします。 実際の通話で計った
エンドツーエンドの遅延は、およそ 3 秒です。
手強かったところは
この 3 つ
セッションは約 10 分で切れる
接続が切れても理由がわからない
新しいモデルをどう見つけ、どう乗り換えるか
開発中に出てきた課題はこの 3 つです。それぞれ簡単に説明します。
ソケットを途切れずに切り替える
Live API のソケットは約 10 分で必ず切断される。切断の 50 秒前にサーバーから goAway
が届くので、その間に次のソケットへ切り替えておく必要がある。
9:00 に goAway
切り替え
ソケット 1
約 10 分。時間が来ると、こちらの状態に関係なくサーバーが切る
ソケット 2
goAway が届いたらすぐ開く。約 200 ms で使える状態になる
返事が来なかったぶんは新しいソケットへ送り直す
切り替えは、ターンが終わったとき、5 秒の無音が続いたとき、期限の 1 秒前。いずれかのタイミングで実行する。
API 自身にも sessionResumption がある。数秒ごとに配られるトークンを持って繋ぎ直すと、会話が復元される。
試したうえで使っていない。復元に一拍かかるうえ、モデルが直前の 1 文をもう一度読み上げてしまい、聞くほうには不具合に聞こえるため。
Live API の WebSocket は約 10 分で切断されます。9 分を過ぎるとサーバーが
goAway メッセージを送ってきて、その 50 秒後に切ります。 そこで Interpretab では、goAway が届いた時点で新しい
Live API セッションを開き、会話のターンが終わったとき、5 秒の無音が続いたとき、期限の 1 秒前、
このいずれか早いタイミングで切り替えます。 古いセッションで翻訳が終わらなかった音声データは、新しいほうへ送り直します。
API 自身の sessionResumption は、試したうえで使っていません。モデルが直前の 1 文をもう一度読み上げてしまうためです。
エラーの扱い
WebSocket が切れると、ブラウザに届くのは close コードという番号だけ。理由は付いてこない。
キーの誤り、使い切った quota、ネットワークの不具合。どれも同じ空っぽの close で、区別する手がかりがない。
close 1011 · 途中で切れる
実行の最中に quota を越えたときだけは、理由の文が付いてくる。ただし
close フレームに入るのは 123 バイトまでで、末尾は切れる。判定は文の先頭で。
どちらも、実際の API 呼び出しで観測した振る舞い。quota 切れのほうは
tests/quota-close.mjs で無料枠の上限を
100 トークンまで下げて再現した。使い切っていてもソケット自体は開くので、10 回リトライすれば 10 回とも繋がって
10 回とも死ぬ。quota が戻るのは太平洋時間の深夜なので、リトライは続けないほうがよい。
何らかの理由により WebSocket が切れたとき、ブラウザに届くのは close コードという番号だけで、理由は付いてきません。キーの間違いや、
quota の不足、ネットワークの不具合、いずれも同じ close コードしか届きません。
そこで事前に、キーが使えるかどうかを HTTP でリクエストし、そのステータスコードを確認します。
セッションの途中で quota を越えたときだけは理由の文が付きますが、123 バイトで切れています。 どちらもリトライはしません。
新しい Live モデルへ自動で移行する
課題 preview のモデルは 2 週間の予告で引退する。Chrome Web Store の審査はそれより長くかかることがある。
モデル名をビルドに焼き込むと、いつかは必ず動かなくなる。
Gemini Live API
GitHub Actions
docs/config.json
全クライアント
毎時
一覧にまだあるか
本番と同じ設定で開くか
毎日 / 引退の検知時
新しいモデルを探す
セッションが開けたものだけ候補にする
モデルが入れ替わるとき
新しいモデル名をコミット
close コードで引退と判別できたときだけ
起動時と 6 時間ごと
設定ファイルを読む
次のソケット切り替えで
実行の途中で乗り換える
ストアの再審査は要らない
{ "schemaVersion" : 1,
"models" : { "simul" : ["gemini-3.5-live-translate-preview" ], "conversation" : ["gemini-3.1-flash-live-preview" ] },
"rates" : { "gemini-3.5-live-translate-preview" : { "audioIn" : 3.5, "audioOut" : 21 }, ... },
"ratesReadAt" : "2026-08-26" , "blockBelowVersion" : "" }
Live API のモデルは速いペースで入れ替わります。モデル名をビルドにハードコードすると、
いつかは必ず動かなくなります。 そこで、1 時間に一度、GitHub Actions が現在利用しているモデルを 1 つずつ実際に呼びます。
一覧にまだあるか、本番と同じ設定でセッションが開くか。 引退を検知したときと、あとは 1 日に一度、新しいモデルを探します。
候補として残るのは、実際にセッションが開けたものだけです。 それを設定ファイルにコミットすると、 全クライアントが
起動時と 6 時間ごとに読みに来て、 次のソケット切り替えで実行の途中から乗り換えます。ストアの再審査は要りません。
エンドツーエンドの長時間 soak テスト
実 API に対する soak
tests/soak.mjs は合成した文をライブセッションに流しつづける。止めるまで走り、訳文は 1 文ずつ別のモデルが採点する。
3 つのユースケースとも同じ挙動だった。1 時間で goAway が 6 回。そのつど引き継いで 7 セッションをつなぎ、サーバーに切られたものはゼロ。文の途中で引き継いだ回も訳文は採点に通った。
ユースケース 長さ 文数 合格 ターン完了
マイク、会話モデル、en → ja 60 分 281 99.3% 1.60 秒
マイク、同時通訳、en → ja 60 分 198 92.4% 0.98 秒
タブの音声、en → ja 15 分 51 92.2% 0.23 秒
同じタブ側のコードで ja → en を 1 時間走らせると、数字はこれよりかなり低くなる。入力が平板な合成音声の日本語だったため。レポートはすべてリポジトリにある。
実際のアプリケーションに対して、エンドツーエンドの soak テストを実施しました。 3 つのユースケースについて、
音声データを 1 文ずつ送り、Live API が返す文字起こしを別のモデルが 1 文ずつ採点しています。 1 時間のあいだに
goAway が 6 回。そのつど引き継いで 7 セッションをつなぎ、サーバーに切られたものはゼロでした。
文の途中で引き継いだ回も、訳文は採点に通っています。
学んだこと
01
まず API を実際に叩いてみる。今回ぶつかったプロトコル上の想定外 3 つのうち 2 つは、どのドキュメントにも書かれていなかった。
400
02
WebSocket はただ切れるだけで、理由を教えてくれない。同じことを HTTP で訊けば、ステータスコードが理由を教えてくれる。
03
Live API は届いた音声から発話の終わりを判定する (VAD)。話し手が黙っている間も、無音を音声として送りつづける必要がある。
04
変わるものをビルドに焼き込まない。モデル名も価格も Google 側の都合で変わるので、外の設定ファイルから読む。
リトライしていいのは通信の不調だけ。quota 切れやキーのエラーは、何秒待ってやり直しても同じ答えが返ってくる。
今回の開発での学びが 4 つ。まず、API を実際に叩いてみること。API の想定外のふるまいは、
多くの場合ドキュメントに書かれていません。 続いて、WebSocket だけでわからないことは HTTP で確認する。 3 つめは、
Live API によるターンの終わりの自動検出のふるまいです。音声データの送信をすぐに止めてしまわず、無音のデータを
送り続けないとモデルはずっと待ち続けてしまいます。 最後に、変わるものをビルドに焼き込まない。
モデル名も価格も頻繁に変わります。
ありがとうございました
個人プロジェクト。Google が作ったものでも、サポートや推奨をしているものでもない。機械翻訳なので、聞き間違えることも、話し手が言っていないことを言うこともある。
Interpretab
Chrome Web Store に出ています。 ガイドは GitHub Pages、 コードは Apache 2.0 です。
個人プロジェクトで、Google の製品ではありません。 ありがとうございました。
ナレーションを再生
0:00 / 0:00