( ctor: SpeechRecognitionConstructor, lang: string, setup: RecognizerSetup )
| 171 | * previous session can't wipe out a newly-started recognizer. |
| 172 | */ |
| 173 | const createRecognizer = ( |
| 174 | ctor: SpeechRecognitionConstructor, |
| 175 | lang: string, |
| 176 | setup: RecognizerSetup |
| 177 | ): SpeechRecognition => { |
| 178 | const recognition = new ctor(); |
| 179 | recognition.continuous = true; |
| 180 | recognition.interimResults = true; |
| 181 | recognition.lang = lang; |
| 182 | |
| 183 | // Ignore events from a session that is no longer the active recognizer (e.g. a |
| 184 | // late onend/onerror after a new session started), so they can't flip the |
| 185 | // listening state, surface a st ale error, or clear a newer recognizer. |
| 186 | const isActive = () => setup.recognitionRef.current === recognition; |
| 187 | |
| 188 | recognition.onstart = () => { |
| 189 | setup.setListening(true); |
| 190 | setup.onListeningChange(true); |
| 191 | }; |
| 192 | recognition.onresult = event => { |
| 193 | if (!isActive()) { |
| 194 | return; |
| 195 | } |
| 196 | const {interim, final} = readTranscript(event); |
| 197 | if (final) { |
| 198 | setup.appendFinal(final); |
| 199 | } |
| 200 | setup.setInterim(interim); |
| 201 | }; |
| 202 | recognition.onerror = event => { |
| 203 | if (!isActive()) { |
| 204 | return; |
| 205 | } |
| 206 | const code = mapErrorCode(event.error); |
| 207 | if (code) { |
| 208 | setup.setError(code); |
| 209 | setup.onError(code); |
| 210 | } |
| 211 | setup.setListening(false); |
| 212 | setup.onListeningChange(false); |
| 213 | setup.setInterim(''); |
| 214 | // Clear so the next press starts a fresh session instead of calling stop(). |
| 215 | setup.recognitionRef.current = null; |
| 216 | }; |
| 217 | recognition.onend = () => { |
| 218 | if (!isActive()) { |
| 219 | return; |
| 220 | } |
| 221 | setup.setListening(false); |
| 222 | setup.onListeningChange(false); |
| 223 | setup.setInterim(''); |
| 224 | setup.recognitionRef.current = null; |
| 225 | }; |
| 226 | |
| 227 | return recognition; |
| 228 | }; |
| 229 | |
| 230 | export const useVoiceInput = (options: VoiceInputProps): VoiceInputResult => { |
no test coverage detected