1
1
<script setup lang="ts">
2
2
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'
4
4
5
5
const voice = deepRef <SpeechSynthesisVoice >(undefined as unknown as SpeechSynthesisVoice )
6
6
const text = shallowRef (' Hello, everyone! Good morning!' )
7
7
const pitch = shallowRef (1 )
8
8
const rate = shallowRef (1 )
9
9
const volume = shallowRef (1 )
10
10
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
+
11
25
const speech = useSpeechSynthesis (text , {
12
26
voice ,
13
27
pitch ,
14
28
rate ,
15
29
volume ,
30
+ onBoundary ,
16
31
})
17
32
18
33
let synth: SpeechSynthesis
@@ -30,12 +45,34 @@ onMounted(() => {
30
45
}
31
46
})
32
47
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
+
33
70
function play() {
34
71
if (speech .status .value === ' pause' ) {
35
- console .log (' resume' )
36
72
window .speechSynthesis .resume ()
37
73
}
38
74
else {
75
+ resetSpeakingText ()
39
76
speech .speak ()
40
77
}
41
78
}
@@ -46,7 +83,14 @@ function pause() {
46
83
47
84
function stop() {
48
85
speech .stop ()
86
+ resetSpeakingText ()
49
87
}
88
+
89
+ watch (() => speech .status .value , (s ) => {
90
+ if (s === ' end' ) {
91
+ resetSpeakingText ()
92
+ }
93
+ })
50
94
</script >
51
95
52
96
<template >
@@ -61,6 +105,12 @@ function stop() {
61
105
<div v-else >
62
106
<label class =" font-bold mr-2" >Spoken Text</label >
63
107
<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 >
64
114
65
115
<br >
66
116
<label class =" font-bold mr-2" >Language</label >
0 commit comments