Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pass interim and final transcripts to onResult callback #51

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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()`
Expand Down
2 changes: 2 additions & 0 deletions examples/src/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -17,6 +18,7 @@ const App = () => (
<Row>
<SpeechSynthesisExample />
<SpeechRecognitionExample />
<SpeechRecognitionContinuousExample />
</Row>
<GitLink>
<img alt="Github" src={gh} />
Expand Down
23 changes: 21 additions & 2 deletions examples/src/shared.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
`;
110 changes: 110 additions & 0 deletions examples/src/useSpeechRecognitionContinuous.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<Container>
<form id="continuous-recognition-form">
<h2>Continuous Recognition</h2>
{!supported && (
<p>
Oh no, it looks like your browser doesn&#39;t support Speech
Recognition.
</p>
)}
{supported && (
<React.Fragment>
<p>
{`Click 'Listen' and start speaking.
SpeechRecognition will provide a transcript of what you are saying.`}
</p>
<label htmlFor="lang">Language</label>
<select
form="speech-recognition-form"
id="lang"
value={lang}
onChange={changeLang}
>
{languageOptions.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
<label>Transcript</label>
<div className="textarea">
{final && <span className="final">{final}</span>}
{interim && <span className="interim">{interim}</span>}
</div>
<button disabled={blocked} type="button" onClick={toggle}>
{listening ? 'Stop' : 'Listen'}
</button>
{blocked && (
<p style={{ color: 'red' }}>
The microphone is blocked for this site in your browser.
</p>
)}
</React.Fragment>
)}
</form>
</Container>
);
};

export default Example;
32 changes: 26 additions & 6 deletions src/useSpeechRecognition.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -60,6 +72,7 @@ const useSpeechRecognition = (props = {}) => {
continuous = false,
maxAlternatives = 1,
grammars,
nonStop = true,
} = args;
setListening(true);
recognition.current.lang = lang;
Expand All @@ -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]);

Expand Down