Skip to content

Commit c0dca93

Browse files
authored
feat(useSpeechSynthesisOptions): add option onBoundary (#4960)
1 parent d03b2a4 commit c0dca93

File tree

2 files changed

+61
-2
lines changed

2 files changed

+61
-2
lines changed

packages/core/useSpeechSynthesis/demo.vue

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,33 @@
11
<script setup lang="ts">
22
import { useSpeechSynthesis } from '@vueuse/core'
3-
import { ref as deepRef, onMounted, shallowRef } from 'vue'
3+
import { computed, ref as deepRef, onMounted, shallowRef, watch } from 'vue'
44
55
const voice = deepRef<SpeechSynthesisVoice>(undefined as unknown as SpeechSynthesisVoice)
66
const text = shallowRef('Hello, everyone! Good morning!')
77
const pitch = shallowRef(1)
88
const rate = shallowRef(1)
99
const volume = shallowRef(1)
1010
11+
const boundaryStart = shallowRef(0)
12+
const boundaryEnd = shallowRef(0)
13+
14+
const textSegments = computed(() => {
15+
const fullText = text.value || ''
16+
const startIndex = Math.max(0, Math.min(boundaryStart.value, fullText.length))
17+
const endIndex = Math.max(startIndex, Math.min(boundaryEnd.value, fullText.length))
18+
return {
19+
leadingText: fullText.slice(0, startIndex),
20+
highlightedText: fullText.slice(startIndex, endIndex),
21+
trailingText: fullText.slice(endIndex),
22+
}
23+
})
24+
1125
const speech = useSpeechSynthesis(text, {
1226
voice,
1327
pitch,
1428
rate,
1529
volume,
30+
onBoundary,
1631
})
1732
1833
let synth: SpeechSynthesis
@@ -30,12 +45,34 @@ onMounted(() => {
3045
}
3146
})
3247
48+
function onBoundary(event: SpeechSynthesisEvent) {
49+
const { charIndex, charLength } = event
50+
const startIndex = charIndex
51+
let endIndex = charIndex
52+
if (typeof charLength === 'number' && charLength > 0) {
53+
endIndex = startIndex + charLength
54+
}
55+
else {
56+
const fullText = text.value || ''
57+
const remainingText = fullText.slice(startIndex)
58+
const firstWordMatch = remainingText.match(/^\S+/)
59+
endIndex = startIndex + (firstWordMatch ? firstWordMatch[0].length : 0)
60+
}
61+
boundaryStart.value = startIndex
62+
boundaryEnd.value = endIndex
63+
}
64+
65+
function resetSpeakingText() {
66+
boundaryStart.value = 0
67+
boundaryEnd.value = 0
68+
}
69+
3370
function play() {
3471
if (speech.status.value === 'pause') {
35-
console.log('resume')
3672
window.speechSynthesis.resume()
3773
}
3874
else {
75+
resetSpeakingText()
3976
speech.speak()
4077
}
4178
}
@@ -46,7 +83,14 @@ function pause() {
4683
4784
function stop() {
4885
speech.stop()
86+
resetSpeakingText()
4987
}
88+
89+
watch(() => speech.status.value, (s) => {
90+
if (s === 'end') {
91+
resetSpeakingText()
92+
}
93+
})
5094
</script>
5195

5296
<template>
@@ -61,6 +105,12 @@ function stop() {
61105
<div v-else>
62106
<label class="font-bold mr-2">Spoken Text</label>
63107
<input v-model="text" class="!inline-block" type="text">
108+
<div class="mt-2" aria-label="current-boundary-preview">
109+
<label class="font-bold mr-2">Speaking Text</label>
110+
<span>{{ textSegments.leadingText }}</span>
111+
<span class="text-primary">{{ textSegments.highlightedText }}</span>
112+
<span>{{ textSegments.trailingText }}</span>
113+
</div>
64114

65115
<br>
66116
<label class="font-bold mr-2">Language</label>

packages/core/useSpeechSynthesis/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ export interface UseSpeechSynthesisOptions extends ConfigurableWindow {
3636
* @default 1
3737
*/
3838
volume?: MaybeRefOrGetter<SpeechSynthesisUtterance['volume']>
39+
/**
40+
* Callback function that is called when the boundary event is triggered.
41+
*/
42+
onBoundary?: (event: SpeechSynthesisEvent) => void
3943
}
4044

4145
/**
@@ -53,6 +57,7 @@ export function useSpeechSynthesis(
5357
rate = 1,
5458
volume = 1,
5559
window = defaultWindow,
60+
onBoundary,
5661
} = options
5762

5863
const synth = window && (window as any).speechSynthesis as SpeechSynthesis
@@ -99,6 +104,10 @@ export function useSpeechSynthesis(
99104
utterance.onerror = (event) => {
100105
error.value = event
101106
}
107+
108+
utterance.onboundary = (event) => {
109+
onBoundary?.(event)
110+
}
102111
}
103112

104113
const utterance = computed(() => {

0 commit comments

Comments
 (0)