Давайте поговорим об использовании JavaScript для синтеза речи, то есть о преобразовании текста в речь. Это довольно удобно и можно использовать, например для того, чтобы браузер читал текст вслух. Всё, что будет рассмотрено далее, делается с помощью чистого JavaScript и его SpeechSynthesis API. Увидите, что начать будет на удивление легко, однако когда погрузитесь в этот процесс, начнётся борьба с особенностями реализации для разных браузеров и платформ.

Для просмотра примеров в этой статье понадобится включить звук.
Быстрый старт
Вот базовый пример кода для преобразования текста в речь.
const utterance = new SpeechSynthesisUtterance('Добрый день!');
window.speechSynthesis.speak(utterance);
Серьезно, это – всё, что необходимо для начала преобразования текста в речь! Вот пример, который можно послушать.
В этом примере есть строка кода, которая требует пояснений.
window.speechSynthesis.cancel();
Если window.speechSynthesis уже что-то говорит, и ему передаётся новый текст, то он будет добавлен в очередь. Поэтому в примере сперва отменяется всё, что уже воспроизводится и чтение текста начинается с начала. Это всё, что делает метод cancel().
Настройка голоса
Вы можете изменять голос, используемый при чтении текста. Набор доступных голосов будет отличаться в зависимости от браузера и ОС. Чтобы получить массив доступных голосов (SpeechSynthesisVoice) можно использовать метод getVoices():
const voices = window.speechSynthesis.getVoices();
Имейте в виду, что не все голоса доступны сразу, отдельные голоса могут добавляться асинхронно. Когда добавляются новые голоса – определить не так сложно:
window.speechSynthesis.onvoiceschanged = function() {
const updatedVoices = window.speechSynthesis.getVoices();
};
Чтобы использовать голос, надо выбрать его из массива и установить для объекта SpeechSynthesisUtterance перед чтением текста. Вот пример, в котором используется последний голос из массива:
const voices = window.speechSynthesis.getVoices();
const lastVoice = voices[voices.length - 1];
const utterance = new SpeechSynthesisUtterance('Добрый день!');
utterance.voice = lastVoice; // заменить голос
window.speechSynthesis.speak(utterance);
Ещё можно настроить параметры высоты тона (pitch), скорости (rate) и громкости (volume). Все они, если не указано иное, по умолчанию равны 1.
const utterance = new SpeechSynthesisUtterance('Добрый день!');
utterance.pitch = 0.7; // пониже
utterance.rate = 1.4; // побыстрее
utterance.volume = 0.8; // потише
window.speechSynthesis.speak(utterance);
Вот здесь начинается беспорядок. Разные голоса могут иметь разные диапазоны значений высоты тона pitch и скорости rate. Вдобавок к этому, у разных браузеров есть свои особенности при настройке этих свойств. Эти причуды будут рассмотрены в конце этой статьи.
Эксперименты показали, что безопаснее всего использовать значения pitch и rate от 0.1 до 2 включительно. К счастью, volume (громкость) настраивается без сюрпризов: от 0 до 1.
В-общем, дальше надо попробовать все это.
Пауза и продолжение воспроизведения
Воспроизведение речи можно приостановить и возобновить. Это довольно просто.
window.speechSynthesis.pause();
window.speechSynthesis.resume();
К сожалению, пауза/продолжение не работают на Android. Об этих особенностях подробнее будет рассказано далее в этой статье.
Чтобы узнать, приостановлено ли воспроизведение речи, надо проверить свойство window.speechSynthesis.paused. Чтобы узнать, идет ли воспроизведение речи, надо проверить свойство window.speechSynthesis.speaking. Стоит обратить внимание, что они не исключают друг друга. Свойство speaking может быть установлено, даже если воспроизведение приостановлено. Кроме того, можно находиться в состоянии paused, даже когда нечего воспроизводить.
События
Можно прослушивать события объекта SpeechSynthesisUtterance и реагировать на них.
startвозникает в начале воспроизведения.pauseвозникает, когда воспроизведение приостановлено.resumeвозникает, когда воспроизведение продолжено.endвозникает, когда закончился текст для воспроизведения. Кроме Safari, в остальных браузерах это событие срабатывает когда воспроизведение ещё и отменено.boundaryвозникает, когда речь достигает нового слова или предложения. Это событие не работает на Android, но об этом чуть позже.
Все эти события SpeechSynthesisEvent возвращают несколько дополнительных свойств, которые могут пригодиться. Вот пример обращения с одним из них.
const utterance = new SpeechSynthesisUtterance('Это всё слова!');
utterance.addEventListener('pause', function(event) {
console.log('Приостановлено после ' + event.elapsedTime + 'ms.');
});
window.speechSynthesis.speak(utterance);
События boundary возвращают свойство name, которое будет установлено, как word или sentence, в зависимости от ситуации. Ещё события boundary во всех браузерах возвращают значение charIndex и charLength, но charLength не возвращается в Safari. Собрав их вместе, можно определить, какое слово в тексте произносится в момент срабатывания события.
В этом примере есть кнопки для воспроизведения, паузы и остановки разговора. Здесь реализовано прослушивание событий start, pause, resume и end для доступности кнопок, когда это необходимо. Для выделения текущего слова используются события boundary.
Особенности, зависимые от браузера/платформы
Синтез речи на JavaScript имеет неплохую поддержку браузерами, но всё ещё считается экспериментальным. Его легко использовать на базовом уровне, но если понадобится более глубокое погружение, придётся столкнуться с некоторыми проблемами и особенностями поведения. Далее будут перечислены некоторые ошибки и подводные камни, чтобы было легче с ними разобраться.
Использование различных SpeechSynthesisVoice для голоса:
- [Chrome/Firefox для Android] на устройстве невозможно изменить голос по умолчанию.
- [Safari] в
window.speechSynthesisне определенaddEventListener, поэтому его не получится использовать для отслеживания событияvoiceschanged. Вместо него надо использоватьonvoiceschanged, как описано здесь. - [Safari] надо помнить, что
SpeechSynthesisVoice.nameне всегда уникален. Если нужен уникальный идентификатор для голосов, следует использоватьSpeechSynthesisVoice.voiceURI.
Настройка свойств SpeechSynthesisUtterance:
- [Safari] значения
pitch(тональности) от0.5и ниже звучат одинаково. - [Safari] если значение
rate(скорость речи) установлено0.5или выше, а затем оно меняется на менее0.5, то при продолжении воспроизведения сохраняется предыдущая более высокая скорость. - [Edge] при использовании нелокального голоса, любое значение, установленное для
pitch, игнорируется и голос всегда будет звучать, как будто установлено1. - [Chrome] при использовании нелокального голоса, установка
pitchв0вернётся к1. - [Chrome] при использовании нелокального голоса, воспроизведение не произойдёт, если
rateокажется более2. - [все браузеры] некоторые голоса могут вводить дополнительные ограничения для диапазона значений
pitchиrate.
Обработка событий SpeechSynthesisEvent:
- [Chrome/Firefox на Android] событие
boundaryне возникает. - [Safari] событие
endне возникает, если синтез речи остановлен с помощью методаcancel(). - [Safari] событие
boundaryне возвращаетcharLength. - [macOS] у события
boundaryзначениеnameне бываетsentence. Вместо этого будет дополнительное событие со значениемword. - [Chrome/Edge в Windows] события
boundary, которые происходят для предложений, возвращают значениеcharLength, равное0, а не ожидаемую длину предложения. - [Chrome/Edge/Safari] для всех событий, кроме
boundary, значениеcharIndexвсегда равно0.
Остальное
- [Chrome/Firefox на Android] пауза и продолжение воспроизведения не работают.
- [iOS] при включенном переключателе «soft mute» речи не слышно.
- [Android/iOS] есть ещё пара проблем с мобильными устройствами, о которых здесь не упоминалось, но про них можно прочитать в этой полезной статье.
Заключение
Прискорбно видеть так много проблем, особенно на Android, но, опять же, речь идёт об экспериментальной функции.
Преобразование текста в речь (синтез речи) в JavaScript на базовом уровне действительно имеет хорошую кроссбраузерную совместимость – просто надо соблюдать осторожность, если потребуется что-то, выходящее за рамки базового функционала.



