|
| 1 | +/// <reference types="dom-speech-recognition" /> |
| 2 | + |
| 3 | +import { useCallback, useMemo, useState } from 'react' |
| 4 | + |
| 5 | +export interface UseSpeechRecognitionOptions { |
| 6 | + /** @default `true` */ |
| 7 | + continuous?: SpeechRecognition['continuous'] |
| 8 | + /** @default `true` */ |
| 9 | + interimResults?: SpeechRecognition['interimResults'] |
| 10 | + /** @default `en-US` */ |
| 11 | + lang?: SpeechRecognition['lang'] |
| 12 | + /** @default `1` */ |
| 13 | + maxAlternatives?: SpeechRecognition['maxAlternatives'] |
| 14 | +} |
| 15 | + |
| 16 | +export const useSpeechRecognition = (options: UseSpeechRecognitionOptions = {}) => { |
| 17 | + const isSupported = useMemo(() => 'SpeechRecognition' in window || 'webkitSpeechRecognition' in window, []) |
| 18 | + const [isFinal, setIsFinal] = useState(false) |
| 19 | + const [isListening, setIsListening] = useState(false) |
| 20 | + const [result, setResult] = useState<string>() |
| 21 | + const [error, setError] = useState<SpeechRecognitionErrorEvent>() |
| 22 | + |
| 23 | + const speechRecognition = useMemo(() => { |
| 24 | + if (!isSupported) |
| 25 | + return |
| 26 | + |
| 27 | + // eslint-disable-next-line ts/strict-boolean-expressions |
| 28 | + const speechRecognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)() |
| 29 | + |
| 30 | + speechRecognition.continuous = options.continuous ?? true |
| 31 | + speechRecognition.interimResults = options.interimResults ?? true |
| 32 | + speechRecognition.lang = options.lang ?? 'en-US' |
| 33 | + speechRecognition.maxAlternatives = options.maxAlternatives ?? 1 |
| 34 | + |
| 35 | + speechRecognition.onstart = () => { |
| 36 | + setIsListening(true) |
| 37 | + setIsFinal(false) |
| 38 | + } |
| 39 | + |
| 40 | + speechRecognition.onresult = (event) => { |
| 41 | + const currentResult = event.results[event.resultIndex] |
| 42 | + const { transcript } = currentResult[0] |
| 43 | + |
| 44 | + setIsFinal(currentResult.isFinal) |
| 45 | + setResult(transcript) |
| 46 | + setError(undefined) |
| 47 | + } |
| 48 | + |
| 49 | + speechRecognition.onerror = error => |
| 50 | + setError(error) |
| 51 | + |
| 52 | + speechRecognition.onend = () => |
| 53 | + setIsListening(false) |
| 54 | + |
| 55 | + return speechRecognition |
| 56 | + }, [options, isSupported]) |
| 57 | + |
| 58 | + const start = useCallback(() => speechRecognition?.start(), [speechRecognition]) |
| 59 | + const stop = useCallback(() => speechRecognition?.stop(), [speechRecognition]) |
| 60 | + const toggle = useCallback(() => { |
| 61 | + if (isListening) |
| 62 | + speechRecognition?.stop() |
| 63 | + else |
| 64 | + speechRecognition?.start() |
| 65 | + }, [speechRecognition, isListening]) |
| 66 | + |
| 67 | + return { |
| 68 | + error, |
| 69 | + isFinal, |
| 70 | + isListening, |
| 71 | + isSupported, |
| 72 | + result, |
| 73 | + start, |
| 74 | + stop, |
| 75 | + toggle, |
| 76 | + } |
| 77 | +} |
0 commit comments