JS30. Задание 23 Speech Synthesis


Демо: https://js3023.github.io/
Код: https://github.com/js3023/js3023.github.io

После задания на распознавания речи посмотрим как реализуется синтез речи в браузере. Синтез речи работает в большинстве современных браузеров.

Просто озвучить текст можно в три строки кода, об этом шла речь в предыдущей теме.

speechSynthesis.speak(
  new SpeechSynthesisUtterance("Здесь текст")
);


Данное задание предлагает добавление дополнительных возможностей:
- выбора голоса;
- регулировки скорости и тембра голоса;
- окно для ввода текста;
- кнопки для запуска и остановки озвучивания текста.

Разметка:

    <div class="voiceinator">

            <h1>The Voiceinator 5000</h1>

            <select name="voice" id="voices">
                <option value="">Select A Voice</option>
            </select>

            <label for="rate">Rate:</label>
            <input name="rate" type="range" min="0" max="3" value="1" step="0.1">

            <label for="pitch">Pitch:</label>

            <input name="pitch" type="range" min="0" max="2" step="0.1">
            <textarea name="text">Hello! I love JavaScript 👍</textarea>
            <button id="stop">Stop!</button>
            <button id="speak">Speak</button>

        </div>

Перечислю элементы разметки: обёртка "voiceinator", заголовок h1, тег select для выбора голоса, два инпута с типом type="range" для регулировки высоты и скорости голоса, тег textarea для окна для ввода текста, кстати, если потянуть за правый нижний угол - окно растягивается. Это свойство данного тега по умолчанию. И две кнопки: Stop! и Speak

Добавим инпутам с типом type="range" шкалу для изменения характеристик голоса:

<input name="rate" type="range" min="0" max="2" value="1" step="0.1">
<input name="pitch" type="range" min="0" max="2" value="1" step="0.1">


JS-код

1. Находим все переменные:

var msg = new SpeechSynthesisUtterance();
var voices = [];
var voicesDropdown = document.getElementById("voices");
var options = document.querySelectorAll("input, #text");
var speakButton = document.getElementById("speak");
var stopButton = document.getElementById("stop");
msg.text = document.getElementById("text").value;

Здесь:
msg - генератор речи;
voices - массив с голосами для озвучивания;
voicesDropdown - тег select для выбора голоса;
options - две шкалы для изменения характеристик голоса и окно для ввода текста;
speakButton, stopButton - кнопки для запуска и остановки озвучивания речи;
msg.text - текст в окне для ввода текста, который нужно озвучить

2. Функция populateVoices() находит голос для озвучивания из имеющихся в браузере, добавляет его в разметку 

function populateVoices() {
    voices = this.getVoices();
    voicesDropdown.innerHTML = voices
        .filter(voice => voice.lang.includes('ru'))
        .map(voice => `<option value="${voice.name}">${voice.name} (${voice.lang})</option>`)
        .join("");
}


Функция выбора голоса срабатывает только когда происходит событие "voiceschanged" у объекта speechSynthesis, так как раньше она недоступна

speechSynthesis.addEventListener("voiceschanged", populateVoices);

Выбор голоса зависит от языка, браузера и операционной системы. Для английского у меня Хром предлагает четыре варианта голоса, Опера - один. Для русского в Хроме один вариант озвучивания, в Опере ни одного. Firefox 51.0 озвучивать ни на английском ни на русском не умеет.
Ещё один момент. Если выставить язык озвучивания 'ru', то будет озвучиваться и русский и английский текст, но если выставлен язык 'en', русский текст не озвучивается.

3. Функция setVoice() определяет какой голос выбран и устанавливает его в качестве голоса для озвучивания. Эта функция производит поиск по массиву голосов и определяет в качестве голоса для озвучавания тот, который выбран в данный момент

function setVoice() {
    msg.voice = voices.find(voice => voice.name === this.value);
    toggle();
}


Функция setVoice() запускается при выборе голоса в выпадающем списке

voicesDropdown.addEventListener("change", setVoice);

4. Нам нужно, чтобы при каждом изменении настроек голоса или при вводе в окно нового текста, функция озвучивания перезапускалась с новыми настройками
За это отвечает функция toggle

function toggle(startOver = true) {
    speechSynthesis.cancel();
    if (startOver) {
        speechSynthesis.speak(msg);
    }
}


5. Теперь, когда у нас есть синтез речи, работающий на базовом уровне, добавим возможность пользователям редактировать параметры (скорость, тембр и текст).

Все они выбраны и сохранены в options:


var options = document.querySelectorAll("input, #text");

Добавим прослушиватель событий к каждой из этих опций:

options.forEach (option => option.addEventListener ('change', setOption));

Функция setOption()  обновляет speechSynthesis.speak(msg); с новыми параметрами:

function setOption() {
    msg[this.name] = this.value;
    toggle();
}


6. Последний пункт - добавить функционал кнопкам  запуска и остановки озвучиваия
Автор делает это очень красиво и лаконично

speakButton.addEventListener("click", toggle);
stopButton.addEventListener("click", () => toggle(false));


Код полностью:

var msg = new SpeechSynthesisUtterance();
var voices = [];
var voicesDropdown = document.getElementById("voices");
var options = document.querySelectorAll("input, #text");
var speakButton = document.getElementById("speak");
var stopButton = document.getElementById("stop");
msg.text = document.getElementById("text").value;

function populateVoices() {
    voices = this.getVoices();
    voicesDropdown.innerHTML = voices
        .filter(voice => voice.lang.includes("ru"))
        .map(voice => `<option value="${voice.name}">${voice.name} (${voice.lang})</option>`)
        .join("");
}

function setVoice() {
    msg.voice = voices.find(voice => voice.name === this.value);
    toggle();
}

function toggle(startOver = true) {
    speechSynthesis.cancel();
    if (startOver) {
        speechSynthesis.speak(msg);
    }
}

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));