Open3
音声入力について

SpeechRecognition API (Web標準のAPI)

SpeechRecognition API活用事例など

SpeechRecognition API を活用した音声入力と会話が終わったタイミングの検知について
「最後に音声が認識された時点から一定時間(例: 5秒)無音が続いたら自動で音声認識をストップする」Logicのポイント
- 無音タイマー用の state(または ref) を用意し、最後に音声が更新されたタイミングでタイマーを再セットする。
- タイマーが発火したら recognition.stop() を呼び出し、音声入力を停止する。
- ユーザーが手動でトグルや停止をした場合にも、タイマーのクリアが必要。
import { useState, useEffect, useCallback } from "react";
/**
* 音声入力 Input Hook
*
* - 音声入力の状態を管理する。
*/
export const useVoiceInput = () => {
// 音声入力中かどうか
const [isListening, setIsListening] = useState(false);
// 音声入力のテキスト
const [transcript, setTranscript] = useState("");
// 無音タイマーの ID を格納するための state
const [silenceTimer, setSilenceTimer] = useState<number | null>(null);
// @ts-ignore
const SpeechRecognition =
window.SpeechRecognition || window.webkitSpeechRecognition;
type SpeechRecognition = typeof SpeechRecognition;
// 音声認識インスタンス
const [recognition, setRecognition] = useState<SpeechRecognition | null>(null);
useEffect(() => {
if (
(typeof window !== "undefined" && "SpeechRecognition" in window) ||
"webkitSpeechRecognition" in window
) {
const SpeechRecognition =
window.SpeechRecognition || window.webkitSpeechRecognition;
const recognitionInstance = new SpeechRecognition();
recognitionInstance.continuous = true;
recognitionInstance.interimResults = true;
recognitionInstance.lang = "ja-JP";
// 音声認識結果が返ってきたときの処理
recognitionInstance.onresult = (event: SpeechRecognitionEvent) => {
const current = event.resultIndex;
const text = event.results[current][0].transcript;
setTranscript(text);
// 音声が認識されたので、無音タイマーをリセット
resetSilenceTimer(recognitionInstance);
};
// 音声認識を開始したときにも無音タイマーをセット
recognitionInstance.onstart = () => {
resetSilenceTimer(recognitionInstance);
};
setRecognition(recognitionInstance);
}
}, []);
/**
* 無音タイマーをリセットし、一定時間後に音声認識を停止するようセットする
*/
const resetSilenceTimer = useCallback(
(recognitionInstance: SpeechRecognition) => {
// 既存のタイマーがあればクリア
if (silenceTimer) {
clearTimeout(silenceTimer);
}
// 5秒後に自動終了するタイマーをセット(時間は適宜変更)
const timerId = window.setTimeout(() => {
recognitionInstance.stop();
setIsListening(false);
// 状況に応じて transcript のリセットは好みで
// setTranscript("");
}, 5000);
setSilenceTimer(timerId);
},
[silenceTimer]
);
/**
* 音声認識の ON/OFF をトグル
*/
const toggleListening = useCallback(() => {
if (!recognition) return;
if (isListening) {
recognition.stop();
setIsListening(false);
// タイマーもクリアしておく
if (silenceTimer) {
clearTimeout(silenceTimer);
setSilenceTimer(null);
}
} else {
recognition.start();
setIsListening(true);
}
}, [isListening, recognition, silenceTimer]);
/**
* 明示的に会話を終了させる
*/
const stopConversation = useCallback(() => {
if (recognition) {
recognition.stop();
}
setIsListening(false);
setTranscript("");
// タイマーもクリア
if (silenceTimer) {
clearTimeout(silenceTimer);
setSilenceTimer(null);
}
}, [recognition, silenceTimer]);
return {
isListening,
transcript,
toggleListening,
stopConversation,
};
};