A Svelte library that converts speech from the microphone to text and makes it available to your Svelte components. Originally based off the react-speech-recognition library.
NOTE: This is a WIP and still in alpha. There's still a bit of work to do before v1. That being said it's functional and any testing would be appreciated.
useSpeechRecognition
is a Svelte hook that gives a component access to a transcript of speech picked up from the user's microphone.
SpeechRecognition
manages the global state of the Web Speech API, exposing functions to turn the microphone on and off.
Under the hood, it uses Web Speech API. Note that browser support for this API is currently limited, with Chrome having the best experience - see supported browsers for more information.
To install:
npm install --save-dev svelte-speech-recognition
To import in your Svelte code:
import SpeechRecognition, { useSpeechRecognition } from 'svelte-speech-recognition/SpeechRecognition'
The most basic example of a component using this hook would be:
<script lang='ts'>
import SpeechRecognition, { useSpeechRecognition } from 'svelte-speech-recognition/SpeechRecognition';
const {
transcriptStore,
listening,
resetTranscript,
browserSupportsSpeechRecognition
} = useSpeechRecognition();
</script>
This is the final transcript:
{$transcriptStore.finalTranscript}
This is the interim transcript:
{$transcriptStore.interimTranscript}
You can see more examples in the example Svelte app attached to this repo. See Developing.
By default, speech recognition is not supported in all browsers, with the best native experience being available on desktop Chrome. To avoid the limitations of native browser speech recognition, it's recommended that you combine svelte-speech-recognition
with a speech recognition polyfill. Why? Here's a comparison with and without polyfills:
svelte-speech-recognition
will be suitable for use in commercial applicationssvelte-speech-recognition
will still be fine for personal projects or use cases where cross-browser support is not neededsvelte-speech-recognition
currently supports polyfills for the following cloud providers:
You can find the full guide for setting up a polyfill here. Alternatively, here is a quick (and free) example using Speechly:
@speechly/speech-recognition-polyfill
in your web appHere's a component for a push-to-talk button. The basic example above would also work fine.
<script>
import { createSpeechlySpeechRecognition } from '@speechly/speech-recognition-polyfill';
import SpeechRecognition, { useSpeechRecognition } from 'svelte-speech-recognition/SpeechRecognition';
const appId = '<INSERT_SPEECHLY_APP_ID_HERE>';
const SpeechlySpeechRecognition = createSpeechlySpeechRecognition(appId);
SpeechRecognition.applyPolyfill(SpeechlySpeechRecognition);
const {
transcriptStore,
listening,
browserSupportsSpeechRecognition
} = useSpeechRecognition();
const startListening = () => SpeechRecognition.startListening({ continuous: true });
</script>
{#if (!browserSupportsSpeechRecognition)} Browser doesn't support speech recognition {:else}
Microphone: {listening ? 'on' : 'off'}
{$transcriptStore.finalTranscript}
{/if}
## Detecting browser support for Web Speech API
If you choose not to use a polyfill, this library still fails gracefully on browsers that don't support speech recognition. It is recommended that you render some fallback content if it is not supported by the user's browser:
```sv
{#if (!browserSupportsSpeechRecognition)}
// Render some fallback content
{/if}
Without a polyfill, the Web Speech API is largely only supported by Google browsers. As of May 2022, the following browsers support the Web Speech API:
For all other browsers, you can render fallback content using the SpeechRecognition.browserSupportsSpeechRecognition
function described above. Alternatively, as mentioned before, you can integrate a polyfill.
Even if the browser supports the Web Speech API, the user still has to give permission for their microphone to be used before transcription can begin. They are asked for permission when svelte-speech-recognition
first tries to start listening. At this point, you can detect when the user denies access via the isMicrophoneAvailable
state. When this becomes false
, it's advised that you disable voice-driven features and indicate that microphone access is needed for them to work.
{#if (!isMicrophoneAvailable)}
// Render some fallback content
{/if}
Before consuming the transcript, you should be familiar with SpeechRecognition
, which gives you control over the microphone. The state of the microphone is global, so any functions you call on this object will affect all components using useSpeechRecognition
.
To start listening to speech, call the startListening
function.
SpeechRecognition.startListening()
This is an asynchronous function, so it will need to be awaited if you want to do something after the microphone has been turned on.
To turn the microphone off, but still finish processing any speech in progress, call stopListening
.
SpeechRecognition.stopListening()
To turn the microphone off, and cancel the processing of any speech in progress, call abortListening
.
SpeechRecognition.abortListening()
To make the microphone transcript available as a Svelte store in your component. It has the interimTranscript and finalTranscript object, simply add:
const { transcriptStore } = useSpeechRecognition()
To set the transcript to an empty string, you can call the resetTranscript
function provided by useSpeechRecognition
. Note that this is local to your component and does not affect any other components using Speech Recognition.
const { resetTranscript } = useSpeechRecognition()
To respond when the user says a particular phrase, you can pass in a list of commands to the useSpeechRecognition
hook. Each command is an object with the following properties:
command
: This is a string or RegExp
representing the phrase you want to listen for. If you want to use the same callback for multiple commands, you can also pass in an array here, with each value being a string or RegExp
callback
: The function that is executed when the command is spoken. The last argument that this function receives will always be an object containing the following properties:
command
: The command phrase that was matched. This can be useful when you provide an array of command phrases for the same callback and need to know which one triggered itresetTranscript
: A function that sets the transcript to an empty stringmatchInterim
: Boolean that determines whether "interim" results should be matched against the command. This will make your component respond faster to commands, but also makes false positives more likely - i.e. the command may be detected when it is not spoken. This is false
by default and should only be set for simple commands.isFuzzyMatch
: Boolean that determines whether the comparison between speech and command
is based on similarity rather than an exact match. Fuzzy matching is useful for commands that are easy to mispronounce or be misinterpreted by the Speech Recognition engine (e.g. names of places, sports teams, restaurant menu items). It is intended for commands that are string literals without special characters. If command
is a string with special characters or a RegExp
, it will be converted to a string without special characters when fuzzy matching. The similarity that is needed to match the command can be configured with fuzzyMatchingThreshold
. isFuzzyMatch
is false
by default. When it is set to true
, it will pass four arguments to callback
:
command
(with any special characters removed)command
command
and the speechcallback
description abovefuzzyMatchingThreshold
: If the similarity of speech to command
is higher than this value when isFuzzyMatch
is turned on, the callback
will be invoked. You should set this only if isFuzzyMatch
is true
. It takes values between 0
(will match anything) and 1
(needs an exact match). The default value is 0.8
.bestMatchOnly
: Boolean that, when isFuzzyMatch
is true
, determines whether the callback should only be triggered by the command phrase that best matches the speech, rather than being triggered by all matching fuzzy command phrases. This is useful for fuzzy commands with multiple command phrases assigned to the same callback function - you may only want the callback to be triggered once for each spoken command. You should set this only if isFuzzyMatch
is true
. The default value is false
.To make commands easier to write, the following symbols are supported:
*
and will match multi-word text:
'I would like to order *'
:<name>
and will match a single word:
'I am :height metres tall'
(
and )
, and is not required to match the command:
'Pass the salt (please)'
'Pass the salt'
and 'Pass the salt please'
<script lang="ts">
import SpeechRecognition, { useSpeechRecognition } from 'svelte-speech-recognition/SpeechRecognition';
let message = '';
const setMessage = (newMessage: string) => (message = newMessage);
const commands = [
{
command: 'I would like to order *',
callback: (food: string) => setMessage(`Your order is for: ${food}`),
matchInterim: true
},
{
command: 'The weather is :condition today',
callback: (condition: string) => setMessage(`Today, the weather is ${condition}`)
},
{
command: ['Hello', 'Hi'],
callback: ({ command }: { command: string }) =>
setMessage(`Hi there! You said: "${command}"`),
matchInterim: true
},
{
command: 'Beijing',
callback: (command: string, spokenPhrase: string, similarityRatio: number) =>
setMessage(`${command} and ${spokenPhrase} are ${similarityRatio * 100}% similar`),
// If the spokenPhrase is "Benji", the message would be "Beijing and Benji are 40% similar"
isFuzzyMatch: true,
fuzzyMatchingThreshold: 0.2
},
{
command: ['eat', 'sleep', 'leave'],
callback: (command: string) => setMessage(`Best matching command: ${command}`),
isFuzzyMatch: true,
fuzzyMatchingThreshold: 0.2,
bestMatchOnly: true
},
{
command: 'clear',
callback: ({ resetTranscript }: { resetTranscript: any }) => resetTranscript(),
matchInterim: true
}
];
const { transcriptStore, browserSupportsSpeechRecognition } = useSpeechRecognition({ commands });
const startListening = () => SpeechRecognition.startListening({ continuous: true });
</script>
{#if browserSupportsSpeechRecognition}
<div>
<p>{message}</p>
<p>{$transcriptStore.finalTranscript}</p>
</div>
{:else}
<p>Browser does not support speech recognition.</p>
{/if}