From c54bac249eb040fcd390fe1eb16d039806bcfc2c Mon Sep 17 00:00:00 2001 From: James Brill Date: Thu, 15 Apr 2021 18:35:29 +0100 Subject: [PATCH 1/6] [Red] Add tests for continuous listening on Android --- src/android.test.js | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/android.test.js diff --git a/src/android.test.js b/src/android.test.js new file mode 100644 index 0000000..6ae37bd --- /dev/null +++ b/src/android.test.js @@ -0,0 +1,36 @@ +/* eslint-disable import/first */ +jest.mock('./isAndroid', () => () => true) + +import { renderHook } from '@testing-library/react-hooks' +import '../tests/vendor/corti' +import SpeechRecognition, { useSpeechRecognition } from './SpeechRecognition' +import RecognitionManager from './RecognitionManager' + +const mockRecognitionManager = () => { + const recognitionManager = new RecognitionManager(window.SpeechRecognition) + SpeechRecognition.getRecognitionManager = () => recognitionManager + return recognitionManager +} + +describe('SpeechRecognition (Android)', () => { + test('sets browserSupportsContinuousListening to false on Android', async () => { + mockRecognitionManager() + + const { result } = renderHook(() => useSpeechRecognition()) + const { browserSupportsContinuousListening } = result.current + + expect(browserSupportsContinuousListening).toEqual(false) + expect(SpeechRecognition.browserSupportsContinuousListening()).toEqual(false) + }) + + test('sets browserSupportsContinuousListening to true when using polyfill', () => { + const MockSpeechRecognition = class {} + SpeechRecognition.applyPolyfill(MockSpeechRecognition) + + const { result } = renderHook(() => useSpeechRecognition()) + const { browserSupportsContinuousListening } = result.current + + expect(browserSupportsContinuousListening).toEqual(true) + expect(SpeechRecognition.browserSupportsContinuousListening()).toEqual(true) + }) +}) From 23dbab10c2de4e127f0b18660c7819afe2b643b1 Mon Sep 17 00:00:00 2001 From: James Brill Date: Thu, 15 Apr 2021 18:37:35 +0100 Subject: [PATCH 2/6] [Green] Add tests for continuous listening on Android --- src/RecognitionManager.js | 3 ++- src/SpeechRecognition.js | 17 +++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/RecognitionManager.js b/src/RecognitionManager.js index 2c9d3b8..26f9baf 100644 --- a/src/RecognitionManager.js +++ b/src/RecognitionManager.js @@ -68,8 +68,9 @@ export default class RecognitionManager { emitBrowserSupportsSpeechRecognitionChange(browserSupportsSpeechRecognitionChange) { Object.keys(this.subscribers).forEach((id) => { - const { onBrowserSupportsSpeechRecognitionChange } = this.subscribers[id] + const { onBrowserSupportsSpeechRecognitionChange, onBrowserSupportsContinuousListening } = this.subscribers[id] onBrowserSupportsSpeechRecognitionChange(browserSupportsSpeechRecognitionChange) + onBrowserSupportsContinuousListening(browserSupportsSpeechRecognitionChange) }) } diff --git a/src/SpeechRecognition.js b/src/SpeechRecognition.js index d1bf39c..33b6983 100644 --- a/src/SpeechRecognition.js +++ b/src/SpeechRecognition.js @@ -3,6 +3,7 @@ import { concatTranscripts, commandToRegExp, compareTwoStringsUsingDiceCoefficie import { clearTrancript, appendTrancript } from './actions' import { transcriptReducer } from './reducers' import RecognitionManager from './RecognitionManager' +import isAndroid from './isAndroid' const DefaultSpeechRecognition = typeof window !== 'undefined' && @@ -12,6 +13,7 @@ const DefaultSpeechRecognition = window.msSpeechRecognition || window.oSpeechRecognition) let _browserSupportsSpeechRecognition = !!DefaultSpeechRecognition +let _browserSupportsContinuousListening = _browserSupportsSpeechRecognition && !isAndroid() let recognitionManager const useSpeechRecognition = ({ @@ -20,7 +22,10 @@ const useSpeechRecognition = ({ commands = [] } = {}) => { const [recognitionManager] = useState(SpeechRecognition.getRecognitionManager()) - const [browserSupportsSpeechRecognition, setBrowserSupportsSpeechRecognition] = useState(_browserSupportsSpeechRecognition) + const [browserSupportsSpeechRecognition, setBrowserSupportsSpeechRecognition] = + useState(_browserSupportsSpeechRecognition) + const [browserSupportsContinuousListening, setBrowserSupportsContinuousListening] = + useState(_browserSupportsContinuousListening) const [{ interimTranscript, finalTranscript }, dispatch] = useReducer(transcriptReducer, { interimTranscript: recognitionManager.interimTranscript, finalTranscript: '' @@ -131,7 +136,8 @@ const useSpeechRecognition = ({ onListeningChange: setListening, onTranscriptChange: handleTranscriptChange, onClearTranscript: handleClearTranscript, - onBrowserSupportsSpeechRecognitionChange: setBrowserSupportsSpeechRecognition + onBrowserSupportsSpeechRecognitionChange: setBrowserSupportsSpeechRecognition, + onBrowserSupportsContinuousListening: setBrowserSupportsContinuousListening } recognitionManager.subscribe(id, callbacks) @@ -153,7 +159,8 @@ const useSpeechRecognition = ({ finalTranscript, listening, resetTranscript, - browserSupportsSpeechRecognition + browserSupportsSpeechRecognition, + browserSupportsContinuousListening } } const SpeechRecognition = { @@ -165,6 +172,7 @@ const SpeechRecognition = { recognitionManager = new RecognitionManager(PolyfillSpeechRecognition) } _browserSupportsSpeechRecognition = true + _browserSupportsContinuousListening = true }, getRecognitionManager: () => { if (!recognitionManager) { @@ -188,7 +196,8 @@ const SpeechRecognition = { const recognitionManager = SpeechRecognition.getRecognitionManager() await recognitionManager.abortListening() }, - browserSupportsSpeechRecognition: () => _browserSupportsSpeechRecognition + browserSupportsSpeechRecognition: () => _browserSupportsSpeechRecognition, + browserSupportsContinuousListening: () => _browserSupportsContinuousListening } export { useSpeechRecognition } From 6ffbd34984cff2b3b863ecfc882b4571a8cf4f72 Mon Sep 17 00:00:00 2001 From: James Brill Date: Thu, 15 Apr 2021 18:38:28 +0100 Subject: [PATCH 3/6] [Minor] v3.8.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 83d5eaf..90c60b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "react-speech-recognition", - "version": "3.7.0", + "version": "3.8.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index a053ebb..f9efa66 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-speech-recognition", - "version": "3.7.0", + "version": "3.8.0", "description": "💬Speech recognition for your React app", "main": "lib/index.js", "scripts": { From 296080ecb5cb9262744ba68edee6b09cb9022888 Mon Sep 17 00:00:00 2001 From: James Brill Date: Thu, 15 Apr 2021 18:56:56 +0100 Subject: [PATCH 4/6] Update README --- README.md | 12 ++++++++++++ docs/API.md | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/README.md b/README.md index 04b6961..a441cbe 100644 --- a/README.md +++ b/README.md @@ -239,6 +239,18 @@ If you want to listen continuously, set the `continuous` property to `true` when SpeechRecognition.startListening({ continuous: true }) ``` +Be warned that not all browsers have good support for continuous listening. Chrome on Android in particular constantly restarts the microphone, leading to a frustrating and noisy (from the beeping) experience. To avoid enabling continuous listening on these browsers, you can make use of the `browserSupportsContinuousListening` state from `useSpeechRecognition` to detect support for this feature. + +``` +if (browserSupportsContinuousListening) { + SpeechRecognition.startListening({ continuous: true }) +} else { + // Fallback behaviour +} +``` + +Alternatively, you can try one of the [polyfills](docs/POLYFILLS.md) to enable continuous listening on these browsers. + ## Changing language To listen for a specific language, you can pass a language tag (e.g. `'zh-CN'` for Chinese) when calling `startListening`. See [here](docs/API.md#language-string) for a list of supported languages. diff --git a/docs/API.md b/docs/API.md index 66fe408..ed8becf 100644 --- a/docs/API.md +++ b/docs/API.md @@ -91,6 +91,18 @@ if (!browserSupportsSpeechRecognition) { It is recommended that you use this state to decide when to render fallback content rather than `SpeechRecognition.browserSupportsSpeechRecognition()` as this will correctly re-render your component if the browser support changes at run-time (e.g. due to a polyfill being applied). +#### browserSupportsContinuousListening [bool] + +Continuous listening is not supported on all browsers, so it is recommended that you apply some fallback behaviour if your web app uses continuous listening and is running on a browser that doesn't support it: + +``` +if (browserSupportsContinuousListening) { + SpeechRecognition.startListening({ continuous: true }) +} else { + // Fallback behaviour +} +``` + ## SpeechRecognition Object providing functions to manage the global state of the microphone. Import with: From f13a4515909606ecb7b984a79c8da190ebe88726 Mon Sep 17 00:00:00 2001 From: James Brill Date: Thu, 15 Apr 2021 19:03:36 +0100 Subject: [PATCH 5/6] Add Troubleshooting section to README --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a441cbe..7f5717d 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ This version requires React 16.8 so that React hooks can be used. If you're used * [Supported browsers](#supported-browsers) * [Polyfills](docs/POLYFILLS.md) * [API docs](docs/API.md) +* [Troubleshooting](#troubleshooting) * [Version 3 migration guide](docs/V3-MIGRATION.md) * [TypeScript declaration file in DefinitelyTyped](https://github.com/OleksandrYehorov/DefinitelyTyped/blob/master/types/react-speech-recognition/index.d.ts) @@ -259,7 +260,15 @@ To listen for a specific language, you can pass a language tag (e.g. `'zh-CN'` f SpeechRecognition.startListening({ language: 'zh-CN' }) ``` -## How to use `react-speech-recognition` offline? +## Troubleshooting + +### `regeneratorRuntime is not defined` + +If you see the error `regeneratorRuntime is not defined` when using this library, you will need to ensure your web app installs `regenerator-runtime`: +* `npm i --save regenerator-runtime` +* If you are using NextJS, put this at the top of your `_app.js` file: `import 'regenerator-runtime/runtime'`. For any other framework, put it at the top of your `index.js` file + +### How to use `react-speech-recognition` offline? Unfortunately, speech recognition will not function in Chrome when offline. According to the [Web Speech API docs](https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API/Using_the_Web_Speech_API): `On Chrome, using Speech Recognition on a web page involves a server-based recognition engine. Your audio is sent to a web service for recognition processing, so it won't work offline.` From 3fc6265fed558c40d3ff40242699ee78dfbdb76e Mon Sep 17 00:00:00 2001 From: James Brill Date: Thu, 15 Apr 2021 19:08:32 +0100 Subject: [PATCH 6/6] Fix typo --- src/RecognitionManager.js | 4 ++-- src/SpeechRecognition.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/RecognitionManager.js b/src/RecognitionManager.js index 26f9baf..78b4483 100644 --- a/src/RecognitionManager.js +++ b/src/RecognitionManager.js @@ -68,9 +68,9 @@ export default class RecognitionManager { emitBrowserSupportsSpeechRecognitionChange(browserSupportsSpeechRecognitionChange) { Object.keys(this.subscribers).forEach((id) => { - const { onBrowserSupportsSpeechRecognitionChange, onBrowserSupportsContinuousListening } = this.subscribers[id] + const { onBrowserSupportsSpeechRecognitionChange, onBrowserSupportsContinuousListeningChange } = this.subscribers[id] onBrowserSupportsSpeechRecognitionChange(browserSupportsSpeechRecognitionChange) - onBrowserSupportsContinuousListening(browserSupportsSpeechRecognitionChange) + onBrowserSupportsContinuousListeningChange(browserSupportsSpeechRecognitionChange) }) } diff --git a/src/SpeechRecognition.js b/src/SpeechRecognition.js index 33b6983..f8c7d02 100644 --- a/src/SpeechRecognition.js +++ b/src/SpeechRecognition.js @@ -137,7 +137,7 @@ const useSpeechRecognition = ({ onTranscriptChange: handleTranscriptChange, onClearTranscript: handleClearTranscript, onBrowserSupportsSpeechRecognitionChange: setBrowserSupportsSpeechRecognition, - onBrowserSupportsContinuousListening: setBrowserSupportsContinuousListening + onBrowserSupportsContinuousListeningChange: setBrowserSupportsContinuousListening } recognitionManager.subscribe(id, callbacks)