- Let the computer read your text in different voices 📢
Demo: 👉 Click me
const msg = new SpeechSynthesisUtterance();
let voices = [];
const voicesDropdown = document.querySelector('[name="voice"]');
const options = document.querySelectorAll('[type="range"], [name="text"]');
const speakButton = document.querySelector('#speak');
const stopButton = document.querySelector('#stop');
msg.text = document.querySelector('[name="text"]').value;
// generate available voices list
function populateVoices() {
voices = this.getVoices();
voicesDropdown.innerHTML = voices
.filter(voice => voice.lang.includes('en'))
.map(voice => `<option value="${voice.name}">${voice.name} (${voice.lang})</option>`)
.join('');
}
// set current voice to the selected voice
function setVoice() {
msg.voice = voices.find(voice => voice.name === this.value);
toggle();
}
// toggle speech start/stop
function toggle(startOver = true) {
speechSynthesis.cancel();
if (startOver) {
speechSynthesis.speak(msg);
}
}
// set speech speed and pitch
function setOption() {
console.log(this.name, this.value);
msg[this.name] = this.value;
toggle();
}
speechSynthesis.addEventListener('voiceschanged', populateVoices);
voicesDropdown.addEventListener('change', setVoice);
options.forEach(option => option.addEventListener('change', setOption));
speakButton.addEventListener('click', toggle);
stopButton.addEventListener('click', () => toggle(false));For whatever reason, the voicechanged event doesn't fire when the page loads, so I changed the populateVoices function to the function provided on MDN docs and call the function on page load.
function populateVoiceList() {
if (typeof speechSynthesis === 'undefined') {
return;
}
voices = speechSynthesis.getVoices();
for (let i = 0; i < voices.length; i++) {
const option = document.createElement('option');
option.textContent = `${voices[i].name} (${voices[i].lang})`;
if (voices[i].default) {
option.textContent += ' — DEFAULT';
}
option.setAttribute('data-lang', voices[i].lang);
option.setAttribute('data-name', voices[i].name);
voicesDropdown.appendChild(option);
}
}
// call on page load
populateVoiceList();
speechSynthesis.addEventListener('voiceschanged', populateVoiceList);At day 20 we already talked about the SpeechRecognition interface of the Web Speech API. Both SpeechSynthesisUtterance and the SpeechSynthesis are also interfaces provided by the Web Speech API, but they are dealing with text-to-speech functions.
Basically, the SpeechSynthesisUtterance object is an object that contains all the information about the speech, including the text, the language, the speed etc. While SpeechSynthesis provides methods for us to interact with the SpeechSynthesisUtterance object.
To create a new SpeechSynthesisUtterance object, use the new keyword and save it to a variable.
let msg = new SpeechSynthesisUtterance();msg.text: the text content of the speechmsg.lang: the language of the utterancemsg.pitch: the pitch at which the utterance will be spoken atmsg.rate: the speed at which the utterance will be spoken atmsg.voice: the voice that will be used to speak the utterancemsg.volume: the volume that the utterance will be spoken at
In the example solution, he sets the default msg.text to the text in the text area.
msg.text = document.querySelector('[name="text"]').value;SpeechSynthesis.speck() // start the speech
SpeechSynthesis.cancel() // cancel the speech
SpeechSynthesis.pause() // pause the speech
SpeechSynthesis.resume() // restart the speech if pausedIn the example solution, he calls the cancel() and speak() methods for starting and stopping the speech. Note that in the speak() method, the msg argument has to be passed in, so it knows which utterance to speak.
function toggle(startOver = true) {
speechSynthesis.cancel();
if (startOver) {
speechSynthesis.speak(msg);
}
}The getVoices() method of the SpeechSynthesis interface returns a list of all the available voices on the current device. (Thus different browsers or OS might have different built in voices)
In the example solution, he calls the getVoices() method and filter out non-english voices. Then he creates all the options insde the dropdown form by using map() and join().
function populateVoices() {
voices = this.getVoices();
voicesDropdown.innerHTML = voices
.filter(voice => voice.lang.includes('en'))
.map(voice => `<option value="${voice.name}">${voice.name} (${voice.lang})</option>`)
.join('');
}
speechSynthesis.addEventListener('voiceschanged', populateVoices);Note that here the populateVoices() function is called on voicechanged event, which could be used to generate available voices for the user to choose. However, for whatever reason, this event doesn't fire on page load on my pc. Thus in my solution, I have to call it on page load to generate available voices list. (I also changed the function to the example function on MDN docs, and removed the filtering process)
// call on page load
populateVoiceList();
speechSynthesis.addEventListener('voiceschanged', populateVoiceList);By using speak() and cancel() methods, we can start and stop the speech easily. What's worth noticing is that he creates a startOver flag for controlling whether the speech should restart or not. If the flag is set to false, the speech will only be canceled and not starting again.
// toggle speech start/stop
function toggle(startOver = true) {
speechSynthesis.cancel();
if (startOver) {
speechSynthesis.speak(msg);
}
}In the functions for setting voices and other options, we want the speech to restart after we adjust the settings. So we can call toggle() function to restart the speech. Since we don't pass in any argument, the startOver flag is default to true.
function setOption() {
console.log(this.name, this.value);
msg[this.name] = this.value;
toggle();
}As for the speaking and stopping buttons, we can call toggle() for starting the speech and call toggle(false) for stopping the speech. Since we pass in the false argument, only the cancel() method will run.
speakButton.addEventListener('click', toggle);
stopButton.addEventListener('click', () => toggle(false));One thing we might easily neglected is the way we pass in arguments to a function. To tie a function to an event listener, we should only pass in the function name (which means without the ()) as below.
speakButton.addEventListener('click', toggle);What if we want to pass in arguments to the function? The following code is not going to work.
stopButton.addEventListener('click', toggle(false));There are several ways to get the work done. First is to use the function() statement. But it looks kinda long with two extra lines of code.
stopButton.addEventListener('click', function() {
toggle(false)
});Since bind() is a more advanced and complicated concept, here we are only going to explain briefly how it can work in this context.
Bind() method returns a new function which has a this and arguments bound with it. If we assign null for the this parameter, the this of the returned function will refer to the global context, which is window in the browser.
Therefore, we are not using bind() for changing the this here, but to pass in the false argument, which we need to execute the function in the way we wish.
stopButton.addEventListener('click', toggle.bind(null, false));The easiest way might be using an arrow function, which has the same logic as using a function() statement, but with much shorter syntax.
stopButton.addEventListener('click', () => toggle(false));