Skip to content

Commit 860df8c

Browse files
committed
feat(hooks): Introduces useSpeechRecognition
1 parent ebd509f commit 860df8c

12 files changed

+174
-1
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,3 +1036,9 @@ Errored release
10361036
### Fixes
10371037

10381038
- package.json specifiers (exports)
1039+
1040+
## [4.3.0] - 2023-03-19
1041+
1042+
### Adds
1043+
1044+
- `useSpeechRecognition` hook

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ import useSomeHook from 'beautiful-react-hooks/useSomeHook'
8686
## 🎨 Hooks
8787

8888
* [useMutableState](docs/useMutableState.md)
89+
* [useSpeechRecognition](docs/useSpeechRecognition.md)
8990
* [useInfiniteScroll](docs/useInfiniteScroll.md)
9091
* [useObservable](docs/useObservable.md)
9192
* [useEvent](docs/useEvent.md)
@@ -131,7 +132,6 @@ import useSomeHook from 'beautiful-react-hooks/useSomeHook'
131132
* [useQueryParams](docs/useQueryParams.md)
132133
* [useSearchQuery](docs/useSearchQuery.md)
133134
* [useURLSearchParams](docs/useURLSearchParams.md)
134-
*
135135

136136
<div>
137137
<p align="center">

docs/README.es-ES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ $ yarn add beautiful-react-hooks
7777
## 🎨 Hooks
7878

7979
* [useMutableState](useMutableState.md)
80+
* [useSpeechRecognition](useSpeechRecognition.md)
8081
* [useInfiniteScroll](useInfiniteScroll.md)
8182
* [useObservable](useObservable.md)
8283
* [useEvent](useEvent.md)

docs/README.it-IT.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ $ yarn add beautiful-react-hooks
7676
## 🎨 Hooks
7777

7878
* [useMutableState](useMutableState.md)
79+
* [useSpeechRecognition](useSpeechRecognition.md)
7980
* [useInfiniteScroll](useInfiniteScroll.md)
8081
* [useObservable](useObservable.md)
8182
* [useEvent](useEvent.md)

docs/README.jp-JP.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ $ yarn add beautiful-react-hooks
7676
## 🎨 Hooks
7777

7878
* [useMutableState](useMutableState.md)
79+
* [useSpeechRecognition](useSpeechRecognition.md)
7980
* [useInfiniteScroll](useInfiniteScroll.md)
8081
* [useObservable](useObservable.md)
8182
* [useEvent](useEvent.md)

docs/README.pl-PL.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ $ yarn add beautiful-react-hooks
7878
## 🎨 Hooki
7979

8080
* [useMutableState](useMutableState.md)
81+
* [useSpeechRecognition](useSpeechRecognition.md)
8182
* [useInfiniteScroll](useInfiniteScroll.md)
8283
* [useObservable](useObservable.md)
8384
* [useEvent](useEvent.md)

docs/README.pt-BR.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ $ yarn add beautiful-react-hooks
7777
## 🎨 Hooks
7878

7979
* [useMutableState](useMutableState.md)
80+
* [useSpeechRecognition](useSpeechRecognition.md)
8081
* [useInfiniteScroll](useInfiniteScroll.md)
8182
* [useObservable](useObservable.md)
8283
* [useEvent](useEvent.md)

docs/README.uk-UA.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ $ yarn add beautiful-react-hooks
7777
## 🎨 Хуки
7878

7979
* [useMutableState](useMutableState.md)
80+
* [useSpeechRecognition](useSpeechRecognition.md)
8081
* [useInfiniteScroll](useInfiniteScroll.md)
8182
* [useObservable](useObservable.md)
8283
* [useEvent](useEvent.md)

docs/README.zh-CN.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ $ yarn add beautiful-react-hooks
7474
## 🎨 Hooks
7575

7676
* [useMutableState](useMutableState.md)
77+
* [useSpeechRecognition](useSpeechRecognition.md)
7778
* [useInfiniteScroll](useInfiniteScroll.md)
7879
* [useObservable](useObservable.md)
7980
* [useEvent](useEvent.md)

docs/useSpeechRecognition.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# useSpeechSynthesis
2+
3+
A hook that provides an interface for using the [Web_Speech_API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API) to
4+
recognize and transcribe speech in a user's browser.
5+
6+
### Why? 💡
7+
8+
- Abstracts the implementation details of the Web Speech API into a single reusable function.
9+
10+
### Basic Usage:
11+
12+
```jsx harmony
13+
import { Button, Space, Tag, Typography, Input } from 'antd';
14+
import useSpeechRecognition from 'beautiful-react-hooks/useSpeechRecognition';
15+
16+
const SpeechSynthesisDemo = () => {
17+
const [name, setName] = React.useState('Antonio');
18+
const { startRecording, transcript, stopRecording, isRecording, isSupported } = useSpeechRecognition();
19+
20+
return (
21+
<DisplayDemo title="useSpeechSynthesis">
22+
<Space direction="vertical">
23+
<Typography.Paragraph>
24+
Supported: <Tag color={isSupported ? 'green' : 'red'}>{isSupported ? 'Yes' : 'No'}</Tag>
25+
</Typography.Paragraph>
26+
<Button onClick={!isRecording ? startRecording : stopRecording} type="primary">
27+
{isRecording ? 'Stop' : 'Start'} recording
28+
</Button>
29+
<Typography.Paragraph>
30+
{transcript}
31+
</Typography.Paragraph>
32+
</Space>
33+
</DisplayDemo>
34+
);
35+
};
36+
37+
<SpeechSynthesisDemo />
38+
```
39+
40+
<!-- Types -->

src/useSpeechRecognition.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { useCallback, useEffect, useMemo, useState } from 'react'
2+
3+
declare global {
4+
interface SpeechRecognitionEvent extends Event {
5+
results: SpeechRecognitionResultList
6+
}
7+
8+
interface SpeechRecognitionPolyfill {
9+
start: () => void
10+
stop: () => void
11+
abort: () => void
12+
addEventListener: (event: string, callback: (event: SpeechRecognitionEvent) => void) => void
13+
removeEventListener: (event: string, callback: (event: SpeechRecognitionEvent) => void) => void
14+
15+
// eslint-disable-next-line @typescript-eslint/no-misused-new
16+
new(): SpeechRecognitionPolyfill
17+
}
18+
19+
interface Window {
20+
SpeechRecognition?: SpeechRecognitionPolyfill
21+
webkitSpeechRecognition?: SpeechRecognitionPolyfill
22+
}
23+
}
24+
25+
const SpeechRecognition = window.SpeechRecognition ?? window.webkitSpeechRecognition
26+
27+
/**
28+
* A hook that provides an interface for using the Web Speech API to recognize and transcribe speech in a user's browser.
29+
*/
30+
const useSpeechRecognition = () => {
31+
const spInstance = useMemo(() => SpeechRecognition ? new SpeechRecognition() : null, [])
32+
const [isRecording, setIsRecording] = useState(false)
33+
const [transcript, setTranscript] = useState('')
34+
const isSupported = !!spInstance
35+
36+
useEffect(() => {
37+
const getResults = (event: SpeechRecognitionEvent) => {
38+
const nextTranscript = event.results[0][0].transcript
39+
setTranscript(nextTranscript)
40+
}
41+
42+
if (spInstance && isSupported) {
43+
spInstance.addEventListener('result', getResults)
44+
}
45+
return () => {
46+
if (spInstance && isSupported) {
47+
spInstance.stop()
48+
spInstance.abort()
49+
spInstance.removeEventListener('result', getResults)
50+
}
51+
}
52+
}, [spInstance])
53+
54+
const startRecording = useCallback(() => {
55+
if (spInstance && isSupported) {
56+
spInstance.start()
57+
setIsRecording(true)
58+
}
59+
}, [spInstance])
60+
61+
const stopRecording = useCallback(() => {
62+
if (spInstance && isSupported) {
63+
spInstance.stop()
64+
setIsRecording(false)
65+
}
66+
}, [spInstance])
67+
68+
return Object.freeze<UseSpeechRecognitionResult>({
69+
isSupported,
70+
transcript,
71+
isRecording,
72+
startRecording,
73+
stopRecording
74+
})
75+
}
76+
77+
export interface UseSpeechRecognitionResult {
78+
isSupported: boolean
79+
transcript: string
80+
isRecording: boolean
81+
startRecording: () => void
82+
stopRecording: () => void
83+
}
84+
85+
export default useSpeechRecognition

test/useSpeechRecognition.spec.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { cleanup, renderHook } from '@testing-library/react-hooks'
2+
import useSpeechRecognition from '../dist/useSpeechRecognition'
3+
import SpeechSynthesisUtteranceMock from './mocks/SpeechSynthesisUtterance.mock'
4+
import SpeechSynthesisMock from './mocks/SpeechSynthesis.mock'
5+
import assertHook from './utils/assertHook'
6+
7+
describe('useSpeechRecognition', () => {
8+
const originalSpeechSynth = global.speechSynthesis
9+
const originalSpeechSynthesisUtterance = global.SpeechSynthesisUtterance
10+
11+
before(() => {
12+
global.speechSynthesis = SpeechSynthesisMock
13+
global.SpeechSynthesisUtterance = SpeechSynthesisUtteranceMock
14+
})
15+
16+
beforeEach(() => cleanup())
17+
18+
after(() => {
19+
global.SpeechSynthesisUtterance = originalSpeechSynthesisUtterance
20+
global.speechSynthesis = originalSpeechSynth
21+
})
22+
23+
assertHook(useSpeechRecognition)
24+
25+
it('should return an object containing the speak function and the utter', () => {
26+
const { result } = renderHook(() => useSpeechRecognition())
27+
28+
expect(result.current).to.be.an('object')
29+
expect(result.current.startRecording).to.be.a('function')
30+
expect(result.current.stopRecording).to.be.a('function')
31+
expect(result.current.transcript).to.be.a('string')
32+
expect(result.current.isRecording).to.be.a('boolean')
33+
expect(result.current.isSupported).to.be.a('boolean')
34+
})
35+
})

0 commit comments

Comments
 (0)