Currently, I'm making a simple app where text is spoken using the speech synthesis API. I want to highlight the words (bold) as they are being spoken. I currently have a very basic implementation doing this using the 'onboundary' event. However, I'm wondering if there's a better/nicer way of doing it, as my implementation is based on a few presumptions.
var words;
var wordIdx;
var text;
var utterance = new SpeechSynthesisUtterance();
utterance.lang = 'en-UK';
utterance.rate = 1;
window.onload = function(){
document.getElementById('textarea').innerText = 'This is a text area. It is used as a simple test to check whether these words are highlighted as they are spoken using the web speech synthesis API (utterance).';
document.getElementById('playbtn').onclick = function(){
text = document.getElementById('textarea').innerText;
words = text.split(' ');
wordIdx = 0;
utterance.text = text;
speechSynthesis.speak(utterance);
}
utterance.onboundary = function(event){
var e = document.getElementById('textarea');
var it = '';
for(var i = 0; i < words.length; i++){
if(i === wordIdx){
it += '<strong>' + words[i] + '</strong>';
} else {
it += words[i];
}
it += ' ';
}
e.innerHTML = it;
wordIdx++;
}
}
Currently, I'm making a simple app where text is spoken using the speech synthesis API. I want to highlight the words (bold) as they are being spoken. I currently have a very basic implementation doing this using the 'onboundary' event. However, I'm wondering if there's a better/nicer way of doing it, as my implementation is based on a few presumptions.
var words;
var wordIdx;
var text;
var utterance = new SpeechSynthesisUtterance();
utterance.lang = 'en-UK';
utterance.rate = 1;
window.onload = function(){
document.getElementById('textarea').innerText = 'This is a text area. It is used as a simple test to check whether these words are highlighted as they are spoken using the web speech synthesis API (utterance).';
document.getElementById('playbtn').onclick = function(){
text = document.getElementById('textarea').innerText;
words = text.split(' ');
wordIdx = 0;
utterance.text = text;
speechSynthesis.speak(utterance);
}
utterance.onboundary = function(event){
var e = document.getElementById('textarea');
var it = '';
for(var i = 0; i < words.length; i++){
if(i === wordIdx){
it += '<strong>' + words[i] + '</strong>';
} else {
it += words[i];
}
it += ' ';
}
e.innerHTML = it;
wordIdx++;
}
}
Share
Improve this question
asked Jun 30, 2016 at 10:28
BoyUnderTheMoonBoyUnderTheMoon
7711 gold badge12 silver badges31 bronze badges
2 Answers
Reset to default 12Your code doesn't work, but i just wrote an example that works the way you want. Open the fiddle to see it working
var utterance = new SpeechSynthesisUtterance();
var wordIndex = 0;
var global_words = [];
utterance.lang = 'en-UK';
utterance.rate = 1;
document.getElementById('playbtn').onclick = function(){
var text = document.getElementById('textarea').value;
var words = text.split(" ");
global_words = words;
// Draw the text in a div
drawTextInPanel(words);
spokenTextArray = words;
utterance.text = text;
speechSynthesis.speak(utterance);
};
utterance.onboundary = function(event){
var e = document.getElementById('textarea');
var word = getWordAt(e.value,event.charIndex);
// Show Speaking word : x
document.getElementById("word").innerHTML = word;
//Increase index of span to highlight
console.info(global_words[wordIndex]);
try{
document.getElementById("word_span_"+wordIndex).style.color = "blue";
}catch(e){}
wordIndex++;
};
utterance.onend = function(){
document.getElementById("word").innerHTML = "";
wordIndex = 0;
document.getElementById("panel").innerHTML = "";
};
// Get the word of a string given the string and the index
function getWordAt(str, pos) {
// Perform type conversions.
str = String(str);
pos = Number(pos) >>> 0;
// Search for the word's beginning and end.
var left = str.slice(0, pos + 1).search(/\S+$/),
right = str.slice(pos).search(/\s/);
// The last word in the string is a special case.
if (right < 0) {
return str.slice(left);
}
// Return the word, using the located bounds to extract it from the string.
return str.slice(left, right + pos);
}
function drawTextInPanel(words_array){
console.log("Dibujado");
var panel = document.getElementById("panel");
for(var i = 0;i < words_array.length;i++){
var html = '<span id="word_span_'+i+'">'+words_array[i]+'</span> ';
panel.innerHTML += html;
}
}
Please play with the following fiddle :
Highlight spoken word SpeechSynthesis Javascript fiddle
It highlight the spoken word in div with blue, you can customize it with bold style, but the important is the idea.
Note: remember that the onboundary
event is only triggered for the native (local) voice synthesis. Changing the voice as specified in the Google Examples (i.e Google UK English Male) for a google remote voice, will make your code fail as the SpeechSynthesis API seems to only play a sound generated by the google servers.
If you're using React you can use tts-react
. Here's an example loading it from a CDN, but it can also be used as an NPM package.
<!DOCTYPE html>
<html lang="en-US">
<head>
<title>tts-react UMD example</title>
<script src="https://unpkg./react@18/umd/react.development.js"></script>
<script src="https://unpkg./react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg./@babel/standalone/babel.min.js"></script>
<script src="https://unpkg./[email protected]/dist/umd/tts-react.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const root = ReactDOM.createRoot(document.getElementById('root'))
const { TextToSpeech, useTts } = TTSReact
const CustomTTS = ({ children }) => {
const { play, ttsChildren } = useTts({ children, markTextAsSpoken: true })
return (
<>
<button onClick={() => play()}>Play</button>
<div>{ttsChildren}</div>
</>
)
}
root.render(
<>
<CustomTTS>
<p>Highlight words as they are spoken.</p>
</CustomTTS>
<TextToSpeech size="small" markTextAsSpoken>
<p>Highlight words as they are spoken.</p>
</TextToSpeech>
</>
)
</script>
</body>
</html>