Давайте поговорим об использовании 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 на базовом уровне действительно имеет хорошую кроссбраузерную совместимость – просто надо соблюдать осторожность, если потребуется что-то, выходящее за рамки базового функционала.