0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Web Speech APIで録画面接の練習機能を作るときの勘所——日本語音声認識の癖と実装メモ

0
Posted at

録画型の非同期面接(AI面接)を導入する企業が増える一方で、「本番と同じ形式で話す練習」ができる環境はまだ少ないです。自分は就活支援AIツール( https://aijpjobs.com/ja )を運営しており、この春夏で「録画して自分の回答を見直す練習機能」を実装しました。この記事は、その実装で得たWeb Speech API(SpeechRecognition)の日本語まわりの癖と、MediaRecorder との並行稼働でつまずいた点のメモです。同じように面接練習・発話分析・録画レビュー系の機能を作る人の時間を節約できれば嬉しいです。

全体構成

機能要件を整理すると次の3本立てになります。

要素 使うAPI 役割
録画 getUserMedia + MediaRecorder WebMで面接回答を録画し、再生レビューに使う
文字起こし SpeechRecognition 回答をリアルタイムでテキスト化し、後から分析できるようにする
制御 タイマー + UI状態管理 制限時間のカウントダウン、開始/終了、再試行

コードの見通しとしては、「録画系」と「認識系」を完全に分けておくのが重要です。両者は独立して失敗する(マイク権限は通るが認識だけ対応ブラウザ外、など)ので、片方が落ちても録画だけは残る設計にします。

日本語音声認識で効いた設定

SpeechRecognition の基本形は次のようになります。

const rec = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
rec.lang = "ja-JP";
rec.continuous = true;
rec.interimResults = true;

ここで実装感覚として効いたのは次の3点です。

  1. interimResults: true は必須。false だと確定文だけが飛んできて、話している最中のフィードバックが真っ白になります。練習UIとしては「話している文字が目に見える」こと自体に意味があるので、暫定結果をそのまま描画します。
  2. 無音で自動停止する。数秒〜十数秒の沈黙で onend が発火します。面接の回答では「考えて黙る」時間が普通に出るので、onend で自動再開するループを書かないと、途中から文字起こしが空になります。
  3. フィラー語は素通しで保存する。「えー」「あのー」は認識結果にそのまま出てきます。後段でフィラー率を計算するなら、ここで消してはいけません。むしろ生の文字起こしをそのまま保存して、分析側で数えるのが正解でした。

自動再開ループの実装

onend での再開はシンプルですが、再開の連打でエラーになることがあります。実装では、停止フラグを持ってから再開する形に落ち着きました。

let stopped = false;
rec.onend = () => {
  if (stopped) return;
  try { rec.start(); } catch (e) { /* InvalidStateError は無視して次のonendで再試行 */ }
};
rec.onerror = (ev) => {
  // "no-speech" と "aborted" は日常範囲。 "not-allowed" だけ致命的(権限)
  if (ev.error === "not-allowed") stopped = true;
};

ポイントは no-speech エラーを異常扱いしないことです。黙って考える時間が長いユーザーほど頻発するので、これをエラーUIにすると「壊れた」と誤解されます。

MediaRecorder との並行稼働

録画と文字起こしは同じgetUserMediaストリームを共有できます。ストリームを分けて取得すると、ブラウザによってはマイクの排他で後から取った方が失敗します。

const stream = await navigator.mediaDevices.getUserMedia({
  video: { width: 640, height: 480 },
  audio: true,
});
const chunks = [];
const mr = new MediaRecorder(stream, { mimeType: "video/webm" });
mr.ondataavailable = (e) => chunks.push(e.data);
mr.start(1000); // 1秒ごとに分割保存しておくと、途中落ちしても前まで残る

つまずきどころは2つでした。

  • mimeType の対応表を持つ。video/webm;codecs=vp9 が通る環境とそうでない環境があるので、MediaRecorder.isTypeSupported で候補を順に試すのが安全です。
  • iOS Safari では SpeechRecognition の連続動作が実質期待できない。webkitSpeechRecognition は存在しますが、長時間の連続認識が安定しません。現実解は「モバイルでは録画+再生レビューのみ、文字起こしはデスクトップのみ」と機能を分けることでした。

文字起こしから簡易指標を作る

保存した文字起こしは、そのまま3つの指標に落とせます。

指標 計算 目安
話速 総文字数 ÷ 発話秒数 300〜400字/分あたりが聞き取りやすい
フィラー率 (えー・あのー・なんか の出現数)÷ 総文字数 高いほど「言葉に詰まっている」印象
構成 冒頭100字に結論があるか 結論ファーストの判定に使える

ここまで自動で出せると、「なんとなく話してしまった」が数値で残るので、練習の反復効果が上がります。面接対策の文面を機械に評価させると違和感が出る、という話を前回の記事でも書きましたが、測定はする、判定は人間がするという線引きは同じです。

まとめ

  • Web Speech API の日本語認識は、onend 自動再開と暫定結果表示を押さえれば実用になる
  • 録画と認識は同じストリームを共有し、失敗ドメインを分けて設計する
  • 文字起こしは生のまま保存し、分析(話速・フィラー率・構成)は後段に分離する

面接練習機能そのものは運営中のaijpjobsで実際に試せます。録画型AI面接がさらに普及すると、「本番前のリハーサル環境」をどう作るかは、ツールを作る側・使う側どちらでも重要になっていくはずです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?