diff --git a/README.md b/README.md index 48b3eba..437f680 100644 --- a/README.md +++ b/README.md @@ -149,9 +149,9 @@ Called when SpeechRecognition stops listening. #### onResult -`function(string)` +`function(transcript: string, final: string, interim: string)` -Called when SpeechRecognition has a result. It is called with a string containing a transcript of the recognized speech. +Called when SpeechRecognition has a result. It is called with a string containing a transcript of the entire recognized speech, a string containing the final transcript of the most recent recognized speech, and a string containing the interim transcript of current speech. ### Returns @@ -171,6 +171,14 @@ Call to make the browser start listening for input. Every time it processes a re `boolean` _(default: true)_ SpeechRecognition can provide realtime results as it's trying to figure out the best match for the input. Set to false if you only want the final result. +- **continuous** + `boolean` _(default: false)_ + The continuous property of the SpeechRecognition interface controls whether continuous results are returned for each recognition, or only a single result. + +- **nonStop** + `boolean` _(default: true)_ + When set to `true` SpeechRecognition will not stop automatically after inactivity. + #### stop `function()` diff --git a/examples/src/index.jsx b/examples/src/index.jsx index 17cbf34..433a160 100644 --- a/examples/src/index.jsx +++ b/examples/src/index.jsx @@ -2,6 +2,7 @@ import React from 'react'; import { render } from 'react-dom'; import SpeechSynthesisExample from './useSpeechSynthesis'; import SpeechRecognitionExample from './useSpeechRecognition'; +import SpeechRecognitionContinuousExample from './useSpeechRecognitionContinuous'; import { GlobalStyles, Row, GitLink, Title } from './shared'; import gh from './images/github.png'; @@ -17,6 +18,7 @@ const App = () => ( + Github diff --git a/examples/src/shared.js b/examples/src/shared.js index 67ae730..61be6b8 100644 --- a/examples/src/shared.js +++ b/examples/src/shared.js @@ -58,16 +58,35 @@ export const Container = styled.div` } select, - textarea { + textarea, + .textarea { font-size: 16px; margin-bottom: 12px; width: 100%; } - textarea { + textarea, + .textarea { border: 1px solid darkgrey; border-radius: 10px; padding: 8px; resize: none; } + + .textarea { + box-sizing: border-box; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + min-height: 72px; + background-color: white; + font-family: monospace; + } + + .final { + color: #666; + } + + .interim { + color: black; + } `; diff --git a/examples/src/useSpeechRecognitionContinuous.jsx b/examples/src/useSpeechRecognitionContinuous.jsx new file mode 100644 index 0000000..31abca9 --- /dev/null +++ b/examples/src/useSpeechRecognitionContinuous.jsx @@ -0,0 +1,110 @@ +import React, { useState } from 'react'; +import { useSpeechRecognition } from '../../src'; +import { Container } from './shared'; + +const languageOptions = [ + { label: 'Cambodian', value: 'km-KH' }, + { label: 'Deutsch', value: 'de-DE' }, + { label: 'English', value: 'en-AU' }, + { label: 'Farsi', value: 'fa-IR' }, + { label: 'Français', value: 'fr-FR' }, + { label: 'Italiano', value: 'it-IT' }, + { label: '普通话 (中国大陆) - Mandarin', value: 'zh' }, + { label: 'Portuguese', value: 'pt-BR' }, + { label: 'Español', value: 'es-MX' }, + { label: 'Svenska - Swedish', value: 'sv-SE' }, +]; + +const Example = () => { + const [lang, setLang] = useState('en-AU'); + const [final, setFinal] = useState(''); + const [interim, setInterim] = useState(''); + const [blocked, setBlocked] = useState(false); + + const onEnd = () => { + setFinal(prevState => `${prevState}${interim} `); + setInterim(''); + }; + + const onResult = (_, finalTranscript, interimTranscript) => { + setInterim(interimTranscript); + setFinal(prevState => `${prevState}${finalTranscript}`); + }; + + const changeLang = (event) => { + setLang(event.target.value); + }; + + const onError = (event) => { + if (event.error === 'not-allowed') { + setBlocked(true); + } + }; + + const { listen, listening, stop, supported } = useSpeechRecognition({ + onResult, + onEnd, + onError, + }); + + const toggle = listening + ? stop + : () => { + setBlocked(false); + listen({ + continuous: true, + nonStop: false, + lang, + }); + }; + + return ( + +
+

Continuous Recognition

+ {!supported && ( +

+ Oh no, it looks like your browser doesn't support Speech + Recognition. +

+ )} + {supported && ( + +

+ {`Click 'Listen' and start speaking. + SpeechRecognition will provide a transcript of what you are saying.`} +

+ + + +
+ {final && {final}} + {interim && {interim}} +
+ + {blocked && ( +

+ The microphone is blocked for this site in your browser. +

+ )} +
+ )} +
+
+ ); +}; + +export default Example; diff --git a/src/useSpeechRecognition.js b/src/useSpeechRecognition.js index ac3695a..bf0737d 100644 --- a/src/useSpeechRecognition.js +++ b/src/useSpeechRecognition.js @@ -36,12 +36,24 @@ const useSpeechRecognition = (props = {}) => { const [supported, setSupported] = useState(false); const processResult = (event) => { - const transcript = Array.from(event.results) - .map((result) => result[0]) - .map((result) => result.transcript) - .join(''); + const merge = (arr) => ( + arr.map((result) => result[0]) + .map((result) => result.transcript) + .join('') + ); - onResult(transcript); + const results = Array.from(event.results); + const changed = results.splice(event.resultIndex) + + const transcript = merge(results); + const final = merge( + changed.filter((result) => result.isFinal) + ); + const interim = merge( + changed.filter((result) => !result.isFinal) + ); + + onResult(transcript, final, interim); }; const handleError = (event) => { @@ -60,6 +72,7 @@ const useSpeechRecognition = (props = {}) => { continuous = false, maxAlternatives = 1, grammars, + nonStop = true, } = args; setListening(true); recognition.current.lang = lang; @@ -73,7 +86,14 @@ const useSpeechRecognition = (props = {}) => { } // SpeechRecognition stops automatically after inactivity // We want it to keep going until we tell it to stop - recognition.current.onend = () => recognition.current.start(); + recognition.current.onend = () => { + setListening(false); + onEnd(); + if (nonStop) { + setListening(true); + recognition.current.start(); + } + } recognition.current.start(); }, [listening, supported, recognition]);