Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(useSpeechSynthesis): new function (#837)
* feat(useSpeechSynthesis): new function * chore: use longer text for test
- Loading branch information
Showing
6 changed files
with
295 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
<script setup lang="ts"> | ||
import { ref } from 'vue-demi' | ||
import { useSpeechSynthesis } from '.' | ||
const lang = ref('en-US') | ||
const text = ref('Hello, everyone! Good morning!') | ||
const speech = useSpeechSynthesis(text, { | ||
lang, | ||
}) | ||
console.log(speech.isPlaying.value) | ||
let synth: SpeechSynthesis | ||
const voices = ref<SpeechSynthesisVoice[]>([]) | ||
if (speech.isSupported) { | ||
// load at last | ||
setTimeout(() => { | ||
synth = window.speechSynthesis | ||
voices.value = synth.getVoices() | ||
}) | ||
} | ||
const play = () => { | ||
if (speech.status.value === 'pause') { | ||
console.log('resume') | ||
window.speechSynthesis.resume() | ||
} | ||
else { | ||
speech.speak() | ||
} | ||
} | ||
const pause = () => { | ||
window.speechSynthesis.pause() | ||
} | ||
const stop = () => { | ||
window.speechSynthesis.cancel() | ||
} | ||
</script> | ||
|
||
<template> | ||
<div> | ||
<div v-if="!speech.isSupported"> | ||
Your browser does not support SpeechSynthesis API, | ||
<a | ||
href="https://caniuse.com/mdn-api_speechsynthesis" | ||
target="_blank" | ||
>more details</a> | ||
</div> | ||
<div v-else> | ||
<label class="font-bold mr-2">Spoken Text</label> | ||
<input v-model="text" class="!inline-block" type="text" /> | ||
|
||
<br /> | ||
<label class="font-bold mr-2">Language</label> | ||
<select v-model="lang" class="ml-5 border h-9 w-50 outline-none"> | ||
<option disabled> | ||
Select Language | ||
</option> | ||
<option | ||
v-for="(voice, i) in voices" | ||
:key="i" | ||
:value="voice.lang" | ||
> | ||
{{ `${voice.name} (${voice.lang})` }} | ||
</option> | ||
</select> | ||
|
||
<div class="mt-2"> | ||
<button | ||
:disabled="speech.isPlaying.value" | ||
@click="play" | ||
> | ||
{{ speech.status.value === 'pause' ? 'Resume' : 'Speak' }} | ||
</button> | ||
<button :disabled="!speech.isPlaying.value" class="orange" @click="pause"> | ||
Pause | ||
</button> | ||
<button :disabled="!speech.isPlaying.value" class="red" @click="stop"> | ||
Stop | ||
</button> | ||
</div> | ||
</div> | ||
</div> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
--- | ||
category: Sensors | ||
--- | ||
|
||
# useSpeechSynthesis | ||
|
||
Reactive [SpeechSynthesis](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis). | ||
|
||
> [Can I use?](https://caniuse.com/mdn-api_speechsynthesis) | ||
## Usage | ||
|
||
```ts | ||
import { useSpeechSynthesis } from '@vueuse/core' | ||
|
||
const { | ||
isSupported, | ||
isPlaying, | ||
status, | ||
voiceInfo, | ||
utterance, | ||
error, | ||
|
||
toggle, | ||
speak | ||
} = useSpeechSynthesis() | ||
``` | ||
|
||
### Options | ||
|
||
The following shows the default values of the options, they will be directly passed to [SpeechSynthesis API](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis). | ||
|
||
```ts | ||
{ | ||
lang: 'en-US', | ||
pitch: 1, | ||
rate: 1, | ||
volume: 1, | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
import { tryOnScopeDispose, MaybeRef } from '@vueuse/shared' | ||
import { Ref, ref, watch, shallowRef, unref, computed } from 'vue-demi' | ||
import { ConfigurableWindow, defaultWindow } from '../_configurable' | ||
|
||
export type Status = 'init' | 'play' | 'pause' | 'end' | ||
|
||
export type VoiceInfo = Pick<SpeechSynthesisVoice, 'lang' | 'name'> | ||
|
||
export interface SpeechSynthesisOptions extends ConfigurableWindow { | ||
/** | ||
* Language for SpeechSynthesis | ||
* | ||
* @default 'en-US' | ||
*/ | ||
lang?: MaybeRef<string> | ||
/** | ||
* Gets and sets the pitch at which the utterance will be spoken at. | ||
* | ||
* @default 1 | ||
*/ | ||
pitch?: SpeechSynthesisUtterance['pitch'] | ||
/** | ||
* Gets and sets the speed at which the utterance will be spoken at. | ||
* | ||
* @default 1 | ||
*/ | ||
rate?: SpeechSynthesisUtterance['rate'] | ||
/** | ||
* Gets and sets the voice that will be used to speak the utterance. | ||
*/ | ||
voice?: SpeechSynthesisVoice | ||
/** | ||
* Gets and sets the volume that the utterance will be spoken at. | ||
* | ||
* @default 1 | ||
*/ | ||
volume?: SpeechSynthesisUtterance['volume'] | ||
} | ||
|
||
/** | ||
* Reactive SpeechSynthesis. | ||
* | ||
* @see https://vueuse.org/useSpeechSynthesis | ||
* @see https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis SpeechSynthesis | ||
* @param options | ||
*/ | ||
export function useSpeechSynthesis(text: MaybeRef<string>, options: SpeechSynthesisOptions = {}) { | ||
const { | ||
pitch = 1, | ||
rate = 1, | ||
volume = 1, | ||
window = defaultWindow, | ||
} = options | ||
|
||
const synth = window && (window as any).speechSynthesis as SpeechSynthesis | ||
const isSupported = Boolean(synth) | ||
|
||
const isPlaying = ref(false) | ||
const status = ref<Status>('init') | ||
|
||
const voiceInfo = { | ||
lang: options.voice?.lang || 'default', | ||
name: options.voice?.name || '', | ||
} | ||
|
||
const spokenText = ref(text || '') | ||
const lang = ref(options.lang || 'en-US') | ||
const error = shallowRef(undefined) as Ref<SpeechSynthesisErrorEvent | undefined> | ||
|
||
const toggle = (value = !isPlaying.value) => { | ||
isPlaying.value = value | ||
} | ||
|
||
const bindEventsForUtterance = (utterance: SpeechSynthesisUtterance) => { | ||
utterance.lang = unref(lang) | ||
|
||
options.voice && (utterance.voice = options.voice) | ||
utterance.pitch = pitch | ||
utterance.rate = rate | ||
utterance.volume = volume | ||
|
||
utterance.onstart = () => { | ||
isPlaying.value = true | ||
status.value = 'play' | ||
} | ||
|
||
utterance.onpause = () => { | ||
isPlaying.value = false | ||
status.value = 'pause' | ||
} | ||
|
||
utterance.onresume = () => { | ||
isPlaying.value = true | ||
status.value = 'play' | ||
} | ||
|
||
utterance.onend = () => { | ||
isPlaying.value = false | ||
status.value = 'end' | ||
} | ||
|
||
utterance.onerror = (event) => { | ||
error.value = event | ||
} | ||
|
||
utterance.onend = () => { | ||
isPlaying.value = false | ||
utterance.lang = unref(lang) | ||
} | ||
} | ||
|
||
const utterance = computed(() => { | ||
isPlaying.value = false | ||
status.value = 'init' | ||
const newUtterance = new SpeechSynthesisUtterance(spokenText.value) | ||
bindEventsForUtterance(newUtterance) | ||
return newUtterance | ||
}) | ||
|
||
const speak = () => { | ||
synth!.cancel() | ||
utterance && synth!.speak(utterance.value) | ||
} | ||
|
||
if (isSupported) { | ||
bindEventsForUtterance(utterance.value) | ||
|
||
watch(lang, (lang) => { | ||
if (utterance.value && !isPlaying.value) | ||
utterance.value.lang = lang | ||
}) | ||
|
||
watch(isPlaying, () => { | ||
if (isPlaying.value) | ||
synth!.resume() | ||
else | ||
synth!.pause() | ||
}) | ||
} | ||
|
||
tryOnScopeDispose(() => { | ||
isPlaying.value = false | ||
}) | ||
|
||
return { | ||
isSupported, | ||
isPlaying, | ||
status, | ||
voiceInfo, | ||
utterance, | ||
error, | ||
|
||
toggle, | ||
speak, | ||
} | ||
} | ||
|
||
export type UseSpeechSynthesisReturn = ReturnType<typeof useSpeechSynthesis> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters