Skip to content

Commit

Permalink
feat(hooks): Introduces useSpeechRecognition
Browse files Browse the repository at this point in the history
  • Loading branch information
antonioru committed Mar 19, 2023
1 parent ebd509f commit 860df8c
Show file tree
Hide file tree
Showing 12 changed files with 174 additions and 1 deletion.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -1036,3 +1036,9 @@ Errored release
### Fixes

- package.json specifiers (exports)

## [4.3.0] - 2023-03-19

### Adds

- `useSpeechRecognition` hook
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ import useSomeHook from 'beautiful-react-hooks/useSomeHook'
## 🎨 Hooks

* [useMutableState](docs/useMutableState.md)
* [useSpeechRecognition](docs/useSpeechRecognition.md)
* [useInfiniteScroll](docs/useInfiniteScroll.md)
* [useObservable](docs/useObservable.md)
* [useEvent](docs/useEvent.md)
Expand Down Expand Up @@ -131,7 +132,6 @@ import useSomeHook from 'beautiful-react-hooks/useSomeHook'
* [useQueryParams](docs/useQueryParams.md)
* [useSearchQuery](docs/useSearchQuery.md)
* [useURLSearchParams](docs/useURLSearchParams.md)
*

<div>
<p align="center">
Expand Down
1 change: 1 addition & 0 deletions docs/README.es-ES.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ $ yarn add beautiful-react-hooks
## 🎨 Hooks

* [useMutableState](useMutableState.md)
* [useSpeechRecognition](useSpeechRecognition.md)
* [useInfiniteScroll](useInfiniteScroll.md)
* [useObservable](useObservable.md)
* [useEvent](useEvent.md)
Expand Down
1 change: 1 addition & 0 deletions docs/README.it-IT.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ $ yarn add beautiful-react-hooks
## 🎨 Hooks

* [useMutableState](useMutableState.md)
* [useSpeechRecognition](useSpeechRecognition.md)
* [useInfiniteScroll](useInfiniteScroll.md)
* [useObservable](useObservable.md)
* [useEvent](useEvent.md)
Expand Down
1 change: 1 addition & 0 deletions docs/README.jp-JP.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ $ yarn add beautiful-react-hooks
## 🎨 Hooks

* [useMutableState](useMutableState.md)
* [useSpeechRecognition](useSpeechRecognition.md)
* [useInfiniteScroll](useInfiniteScroll.md)
* [useObservable](useObservable.md)
* [useEvent](useEvent.md)
Expand Down
1 change: 1 addition & 0 deletions docs/README.pl-PL.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ $ yarn add beautiful-react-hooks
## 🎨 Hooki

* [useMutableState](useMutableState.md)
* [useSpeechRecognition](useSpeechRecognition.md)
* [useInfiniteScroll](useInfiniteScroll.md)
* [useObservable](useObservable.md)
* [useEvent](useEvent.md)
Expand Down
1 change: 1 addition & 0 deletions docs/README.pt-BR.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ $ yarn add beautiful-react-hooks
## 🎨 Hooks

* [useMutableState](useMutableState.md)
* [useSpeechRecognition](useSpeechRecognition.md)
* [useInfiniteScroll](useInfiniteScroll.md)
* [useObservable](useObservable.md)
* [useEvent](useEvent.md)
Expand Down
1 change: 1 addition & 0 deletions docs/README.uk-UA.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ $ yarn add beautiful-react-hooks
## 🎨 Хуки

* [useMutableState](useMutableState.md)
* [useSpeechRecognition](useSpeechRecognition.md)
* [useInfiniteScroll](useInfiniteScroll.md)
* [useObservable](useObservable.md)
* [useEvent](useEvent.md)
Expand Down
1 change: 1 addition & 0 deletions docs/README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ $ yarn add beautiful-react-hooks
## 🎨 Hooks

* [useMutableState](useMutableState.md)
* [useSpeechRecognition](useSpeechRecognition.md)
* [useInfiniteScroll](useInfiniteScroll.md)
* [useObservable](useObservable.md)
* [useEvent](useEvent.md)
Expand Down
40 changes: 40 additions & 0 deletions docs/useSpeechRecognition.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# useSpeechSynthesis

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
recognize and transcribe speech in a user's browser.

### Why? 💡

- Abstracts the implementation details of the Web Speech API into a single reusable function.

### Basic Usage:

```jsx harmony
import { Button, Space, Tag, Typography, Input } from 'antd';
import useSpeechRecognition from 'beautiful-react-hooks/useSpeechRecognition';

const SpeechSynthesisDemo = () => {
const [name, setName] = React.useState('Antonio');
const { startRecording, transcript, stopRecording, isRecording, isSupported } = useSpeechRecognition();

return (
<DisplayDemo title="useSpeechSynthesis">
<Space direction="vertical">
<Typography.Paragraph>
Supported: <Tag color={isSupported ? 'green' : 'red'}>{isSupported ? 'Yes' : 'No'}</Tag>
</Typography.Paragraph>
<Button onClick={!isRecording ? startRecording : stopRecording} type="primary">
{isRecording ? 'Stop' : 'Start'} recording
</Button>
<Typography.Paragraph>
{transcript}
</Typography.Paragraph>
</Space>
</DisplayDemo>
);
};

<SpeechSynthesisDemo />
```

<!-- Types -->
85 changes: 85 additions & 0 deletions src/useSpeechRecognition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { useCallback, useEffect, useMemo, useState } from 'react'

declare global {
interface SpeechRecognitionEvent extends Event {
results: SpeechRecognitionResultList
}

interface SpeechRecognitionPolyfill {
start: () => void
stop: () => void
abort: () => void
addEventListener: (event: string, callback: (event: SpeechRecognitionEvent) => void) => void
removeEventListener: (event: string, callback: (event: SpeechRecognitionEvent) => void) => void

// eslint-disable-next-line @typescript-eslint/no-misused-new
new(): SpeechRecognitionPolyfill
}

interface Window {
SpeechRecognition?: SpeechRecognitionPolyfill
webkitSpeechRecognition?: SpeechRecognitionPolyfill
}
}

const SpeechRecognition = window.SpeechRecognition ?? window.webkitSpeechRecognition

/**
* A hook that provides an interface for using the Web Speech API to recognize and transcribe speech in a user's browser.
*/
const useSpeechRecognition = () => {
const spInstance = useMemo(() => SpeechRecognition ? new SpeechRecognition() : null, [])
const [isRecording, setIsRecording] = useState(false)
const [transcript, setTranscript] = useState('')
const isSupported = !!spInstance

useEffect(() => {
const getResults = (event: SpeechRecognitionEvent) => {
const nextTranscript = event.results[0][0].transcript
setTranscript(nextTranscript)
}

if (spInstance && isSupported) {
spInstance.addEventListener('result', getResults)
}
return () => {
if (spInstance && isSupported) {
spInstance.stop()
spInstance.abort()
spInstance.removeEventListener('result', getResults)
}
}
}, [spInstance])

const startRecording = useCallback(() => {
if (spInstance && isSupported) {
spInstance.start()
setIsRecording(true)
}
}, [spInstance])

const stopRecording = useCallback(() => {
if (spInstance && isSupported) {
spInstance.stop()
setIsRecording(false)
}
}, [spInstance])

return Object.freeze<UseSpeechRecognitionResult>({
isSupported,
transcript,
isRecording,
startRecording,
stopRecording
})
}

export interface UseSpeechRecognitionResult {
isSupported: boolean
transcript: string
isRecording: boolean
startRecording: () => void
stopRecording: () => void
}

export default useSpeechRecognition
35 changes: 35 additions & 0 deletions test/useSpeechRecognition.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { cleanup, renderHook } from '@testing-library/react-hooks'
import useSpeechRecognition from '../dist/useSpeechRecognition'
import SpeechSynthesisUtteranceMock from './mocks/SpeechSynthesisUtterance.mock'
import SpeechSynthesisMock from './mocks/SpeechSynthesis.mock'
import assertHook from './utils/assertHook'

describe('useSpeechRecognition', () => {
const originalSpeechSynth = global.speechSynthesis
const originalSpeechSynthesisUtterance = global.SpeechSynthesisUtterance

before(() => {
global.speechSynthesis = SpeechSynthesisMock
global.SpeechSynthesisUtterance = SpeechSynthesisUtteranceMock
})

beforeEach(() => cleanup())

after(() => {
global.SpeechSynthesisUtterance = originalSpeechSynthesisUtterance
global.speechSynthesis = originalSpeechSynth
})

assertHook(useSpeechRecognition)

it('should return an object containing the speak function and the utter', () => {
const { result } = renderHook(() => useSpeechRecognition())

expect(result.current).to.be.an('object')
expect(result.current.startRecording).to.be.a('function')
expect(result.current.stopRecording).to.be.a('function')
expect(result.current.transcript).to.be.a('string')
expect(result.current.isRecording).to.be.a('boolean')
expect(result.current.isSupported).to.be.a('boolean')
})
})

0 comments on commit 860df8c

Please sign in to comment.