Как стать автором
Обновить
360.7
Рейтинг
Маклауд
Облачные серверы на базе AMD EPYC

Наиболее полное руководство по практическому использованию Web Speech API

Блог компании МаклаудРазработка веб-сайтовJavaScriptAPI

В этой статье я хочу поделиться с вами результатами изучения основных возможностей Web Speech API (далее — WSA).


Введение


WSA — это экспериментальная технология, состоящая из двух интерфейсов: SpeechSynthesis (интерфейс для перевода текста в речь) и SpeechRecognition (интерфейс для распознавания речи).


О том, что из себя представляют названные интерфейсы и что в себя включают можно почитать на MDN или в рабочем черновике (данный черновик, в отличие от большинства спецификаций, написан более-менее человеческим языком).


Что касается поддержки, то вот что об этом говорит Can I use:




Как мы видим, SpeechSynthesis поддерживается большинством современных браузеров. К сожалению, поддержка SpeechRecognition оставляет желать лучшего. Я говорю "к сожалению", поскольку SpeechRecognition предоставляет более интересные возможности с точки зрения практического применения в веб-приложениях, в чем вы сами убедитесь после прочтения статьи. Полагаю, такая ситуация обусловлена сложностью технической реализации распознавания речи.


Мы с вами реализуем 4 простых приложения, по 2 на каждый интерфейс:


  1. Плеер для озвучивания текста со всеми возможностями, которые предоставляет SpeechSynthesis, включая выбор языка (голоса)
  2. Страницу с возможностью озвучивания ее текстового содержимого
  3. "Диктофон" для распознавания речи и ее перевода в текст с автоматическим и кастомным форматированием
  4. Одностраничное приложение с голосовым управлением, в том числе, переключение страниц, прокрутка и изменение цветовой темы (или схемы), используемой на сайте

Если вам это интересно, то прошу следовать за мной.


Плеер для озвучивания текста


Наша разметка будет выглядеть следующим образом:


<div id="wrapper">
  <h1>Speech Synthesis - Player</h1>
  <label
    >Text:
    <textarea id="textarea">Привет! Как дела?</textarea>
  </label>
  <label
    >Voice:
    <select id="select"></select>
  </label>
  <label
    >Volume:
    <input id="volume" type="range" min="0" max="1" step="0.1" value="1" />
    <span>1</span>
  </label>
  <label
    >Rate:
    <input id="rate" type="range" min="0" max="3" step="0.5" value="1" />
    <span>1</span>
  </label>
  <label
    >Pitch:
    <input id="pitch" type="range" min="0" max="2" step="0.5" value="1" />
    <span>1</span>
  </label>
  <div id="buttons">
    <button class="speak">Speak</button>
    <button class="cancel">Cancel</button>
    <button class="pause">Pause</button>
    <button class="resume">Resume</button>
  </div>
</div>

У нас имеется поле для ввода текста (textarea), который мы будем озвучивать; выпадающий список (select) для выбора голоса, которым будет озвучиваться текст; инпуты-диапазоны для установки громкости (volume), скорости (rate) воспроизведения и высоты голоса (pitch), а также кнопки для запуска (speak), отмены (cancel), приостановления (pause) и продолжения (resume) воспроизведения. В общем, ничего особенного.


Обратите внимание на атрибуты инпутов min, max, step и value. Значения данных атрибутов взяты из черновика спецификации, но некоторые из них отданы на откуп производителям браузеров, т.е. зависят от конкретной реализации. Также обратите внимание на наличие почти у всех элементов идентификаторов. Мы будем использовать эти id для прямого доступа к элементам в скрипте в целях сокращения кода, однако в реальных приложениях так лучше не делать во избежание загрязнения глобального пространства имен.


Переходим к JavaScript. Создаем экземпляр SpeechSynthesisUtterance ("utterance" можно перевести как "выражение текста словами"):


const U = new SpeechSynthesisUtterance()

Пытаемся получить доступные голоса (именно "пытаемся", поскольку в первый раз, по крайней мере, в Chrome возвращается пустой массив):


let voices = speechSynthesis.getVoices()

При вызове метода getVoices() возникает событие voiceschanged. Обрабатываем это событие для "настоящего" получения голосов и формирования выпадающего списка:


speechSynthesis.onvoiceschanged = () => {
  voices = speechSynthesis.getVoices()
  populateVoices(voices)
}

Объект голоса (назовем его так) выглядит следующим образом:


0: SpeechSynthesisVoice
  default: true
  lang: "de-DE"
  localService: false
  name: "Google Deutsch"
  voiceURI: "Google Deutsch"

Реализуем функцию формирования выпадающего списка:


function populateVoices(voices) {
  // Перебираем голоса и создаем элемент `option` для каждого
  // Текстовым содержимым `option` является название голоса, а значением - индекс голоса в массиве голосов
  voices.forEach((voice, index) => {
    select.options[index] = new Option(voice.name, index)
  })

  // Делаем голосом по умолчанию `Google русский`
  // Он нравится мне больше, чем голос от `Microsoft`
  const defaultVoiceIndex = voices.findIndex(
    (voice) => voice.name === 'Google русский'
  )
  select.selectedIndex = defaultVoiceIndex
  // После этого инициализируем обработчики событий
  initializeHandlers()
}

Функция инициализации обработчиков событий выглядит так:


function initializeHandlers() {
  // Ниже перечислены почти все события, которые возникают при работе с рассматриваемым интерфейсом
  U.onstart = () => console.log('Started')
  U.onend = () => console.log('Finished')
  U.onerror = (err) => console.error(err)
  // Мне не удалось добиться возникновения этих событий
  U.onpause = () => console.log('Paused')
  U.onresume = () => console.log('Resumed')

  // Обработка изменения настроек
  wrapper.onchange = ({ target }) => {
    if (target.type !== 'range') return
    handleChange(target)
  }

  // Обработка нажатия кнопок
  buttons.addEventListener('click', ({ target: { className } }) => {
    // SpeechSynthesis предоставляет таким методы как `speak()`, `cancel()`, `pause()` и `resume()`
    // Вызов метода `speak()` требует предварительной подготовки
    // Кроме того, мы проверяем наличие текста в очереди на озвучивание с помощью атрибута `speaking`
    // Есть еще два атрибута: `pending` и `paused`, но они не всегда возвращают корректные значения
    switch (className) {
      case 'speak':
        if (!speechSynthesis.speaking) {
          convertTextToSpeech()
        }
        break
      case 'cancel':
        return speechSynthesis.cancel()
      case 'pause':
        return speechSynthesis.pause()
      case 'resume':
        return speechSynthesis.resume()
      default:
        return
    }
  })
}

Обработка изменения настроек:


function handleChange(el) {
  el.nextElementSibling.textContent = el.value
}

Функция преобразования текста в речь:


function convertTextToSpeech() {
  // Получаем текст
  const trimmed = textarea.value.trim()
  if (!trimmed) return
  // Передаем его экземпляру `SpeechSynthesisUtterance`
  U.text = trimmed
  // Получаем выбранный голос
  const voice = voices[select.value]
  // Передаем голос и другие настройки экземпляру
  U.voice = voice
  // язык
  U.lang = voice.lang
  // громкость
  U.volume = volume.value
  // скорость
  U.rate = rate.value
  // высота голоса
  U.pitch = pitch.value
  // Запускаем озвучивание!
  speechSynthesis.speak(U)
}

После установки всех настроек наш экземпляр SpeechSynthesisUtterance выглядит следующим образом:


SpeechSynthesisUtterance
  lang: "ru-RU"
  onboundary: null
  onend: () => console.log('Finished')
  onerror: (err) => console.error(err)
  onmark: null
  onpause: () => console.log('Paused')
  onresume: () => console.log('Resumed')
  onstart: () => console.log('Started')
  pitch: 1
  rate: 1
  text: "Привет! Как дела?"
  voice: SpeechSynthesisVoice { voiceURI: "Google русский", name: "Google русский", lang: "ru-RU", localService: false, default: false }
  volume: 1

Уже в процессе написания статьи я решил добавить управление с помощью клавиатуры:


window.onkeydown = ({ key }) => {
  switch (key.toLowerCase()) {
    case 's':
      if (!speechSynthesis.speaking) {
        convertTextToSpeech()
      }
      break
    case 'c':
      return speechSynthesis.cancel()
    case 'p':
      return speechSynthesis.pause()
    case 'r':
      return speechSynthesis.resume()
    default:
      return
  }
}

Поиграть с кодом можно здесь:



Страница с возможность озвучивания текстового содержимого


Что касается SpeechSynthesis, то с точки зрения практического использования данного интерфейса, сложно что-то придумать, кроме создания страниц с возможностью озвучивания размещенного на них текста. Этим мы и займемся.


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


Наша разметка будет выглядеть так:


<div id="wrapper">
  <h1>Speech Synthesis - Page Reader</h1>
  <div>
    <button class="play" tabindex="1"></button>
    <p>
      JavaScript — мультипарадигменный язык программирования. Поддерживает
      объектно-ориентированный, императивный и функциональный стили.
      Является реализацией спецификации ECMAScript (стандарт ECMA-262).
    </p>
  </div>
  <div>
    <button class="play" tabindex="2"></button>
    <p>
      JavaScript обычно используется как встраиваемый язык для программного
      доступа к объектам приложений. Наиболее широкое применение находит в
      браузерах как язык сценариев для придания интерактивности
      веб-страницам.
    </p>
  </div>
  <div>
    <button class="play" tabindex="3"></button>
    <p>
      Основные архитектурные черты: динамическая типизация, слабая
      типизация, автоматическое управление памятью, прототипное
      программирование, функции как объекты первого класса.
    </p>
  </div>
</div>

У нас имеется три блока (div) с кнопками для озвучивания текста (play) и, собственно, текстом, который будет озвучиваться (первые три абзаца статьи по JavaScript из Википедии). Обратите внимание, что я добавил кнопкам атрибут tabindex, чтобы переключаться между ними с помощью tab и нажимать с помощью space. Однако, имейте ввиду, что использовать атрибут tabindex не рекомендуется по причине того, что браузер использует переключение фокуса с помощью tab для повышения доступности.


С вашего позволения, я приведу код скрипта целиком:


// Это часть должна быть вам знакома по предыдущему примеру
let voices = speechSynthesis.getVoices()
let defaultVoice

speechSynthesis.onvoiceschanged = () => {
  voices = speechSynthesis.getVoices()
  defaultVoice = voices.find((voice) => voice.name === 'Google русский')

  wrapper.addEventListener('click', handleClick)
  window.addEventListener('keydown', handleKeydown)
}

const PLAY = 'play'
const PAUSE = 'pause'
const RESUME = 'resume'

function handleClick({ target }) {
  switch (target.className) {
    case PLAY:
      // При нажатии кнопки `play` в момент озвучивания другого текста,
      // нам нужно прекратить текущее озвучивание перед началом нового
      speechSynthesis.cancel()

      const { textContent } = target.nextElementSibling

      // Об этой части см. ниже
      textContent.split('.').forEach((text) => {
        const trimmed = text.trim()
        if (trimmed) {
          const U = getUtterance(target, text)
          speechSynthesis.speak(U)
        }
      })
      break
    case PAUSE:
      // CSS-класс кнопки отвечает за отображаемую рядом с ней иконку
      // ``- ожидание/стоп, `` - озвучивание/воспроизведение, `` - пауза
      // иконка `` используется в качестве индикатора кнопки, находящейся в фокусе
      target.className = RESUME
      speechSynthesis.pause()
      break
    case RESUME:
      target.className = PAUSE
      speechSynthesis.resume()
      break
    default:
      break
  }
}

// При нажатии `escape` прекращаем озвучивание текста
// Можете добавить свои контролы
function handleKeydown({ code }) {
  switch (code) {
    case 'Escape':
      return speechSynthesis.cancel()
    default:
      break
  }
}

function getUtterance(target, text) {
  const U = new SpeechSynthesisUtterance(text)
  U.voice = defaultVoice
  U.lang = defaultVoice.lang
  U.volume = 1
  U.rate = 1
  U.pitch = 1

  // Я специально не стал скрывать смену иконок при начале/окончании воспроизведения
  U.onstart = () => {
    console.log('Started')
    target.className = PAUSE
  }
  U.onend = () => {
    console.log('Finished')
    target.className = PLAY
  }
  U.onerror = (err) => console.error(err)

  return U
}

Тонкий момент в приведенном коде — это преобразование озвучиваемого текста в массив предложений (разделителем является точка (.)), перебор массива, и воспроизведение каждого предложения по отдельности (точнее, помещение всех предложений одного за другим в очередь на озвучивание) — textContent.split('.').forEach(...). Причина такого решения заключается в том, что озвучивание длинного текста тихо обрывается примерно на 220 символе (в Chrome). Черновик спецификации для такого случае предусматривает специальную ошибку text-to-long (текст слишком длинный), но данной ошибки не возникает, озвучивание просто резко прекращается, причем, для восстановления работы SpeechSynthesis зачастую приходится перезагружать вкладку (при запущенном сервере для разработки даже это не всегда срабатывает). Возможно, вам удастся найти другое решение.


Поиграть с кодом можно здесь:



"Диктофон" для распознавания речи


После того, как мы обстоятельно рассмотрели SpeechSynthesis, можно переходить ко второму, немного более сложному, но, вместе с тем, и более интересному интерфейсу, входящему в состав WSASpeechRecoginition.


Вот наша начальная разметка:


<div id="wrapper">
  <h1>Speech Recognition - Dictaphone</h1>
  <textarea id="final_text" cols="30" rows="10"></textarea>
  <input type="text" id="interim_text" />
  <div id="buttons">
    <button class="start">Старт</button>
    <button class="stop">Стоп</button>
    <button class="abort">Сброс</button>
    <button class="copy">Копия</button>
    <button class="clear">Очистить</button>
  </div>
</div>

У нас имеется поле для вставки финального (распознанного) текста (final_text) и поле для вставки промежуточного (находящегося в процессе распознавания) текста (interim_text), а также панель управления (buttons). Выбор элементов textarea и input для хранения текста обусловлен как вариативностью промежуточных результатов, которые меняются на лету, так и необходимостью внесения небольших правок в распознанный текст, что связано с естественным несовершенством перевода устной речи в текст. Стоит отметить, что, в целом, Chrome очень неплохо справляется с задачей распознавания речи и автоматическим форматированием распознанного текста.


Кроме кнопок для управления распознаванием речи (start, stop и abort), мы реализуем возможность копирования распознанного текста в буфер обмена с помощью Clipboard API и очистки соответствующего поля.


Начнем с определения переменных для финального текста и индикатора распознавания:


let final_transcript = ''
let recognizing = false

Далее, создаем и настраиваем экземпляр SpeechRecognition:


// Напоминаю, что `WSA`, в целом, и, особенно, `SpeechRecognition` являются экпериментальными
const speechRecognition =
  window.SpeechRecognition || window.webkitSpeechRecognition
// Создаем экземпляр `SpeechRecognition`
const recognition = new speechRecognition()
// Свойство `continuous` определяет, продолжается ли распознавание речи после получения первого финального результата
recognition.continuous = true
// обработка промежуточных результатов
recognition.interimResults = true
// максимальное количество альтернатив распознанного слова
recognition.maxAlternatives = 3
// язык
recognition.lang = 'ru-RU'

Добавляем обработчики событий запуска, ошибки и окончания распознавания речи:


recognition.onstart = () => {
  console.log('Распознавание голоса запущено')
}
recognition.onerror = ({ error }) => {
  console.error(error)
}
recognition.onend = () => {
  console.log('Распознавание голоса закончено')
  // Снова запускаем распознавание, если индикатор имеет значение `true`
  if (!recognizing) return
  recognition.start()
}

SpeechRecognition хорошо справляется с распознаванием слов, фраз и целых предложений, он даже выполняет вполне уместную "капитализацию" строки. Но он не "понимает" знаков препинания. Для того, чтобы помочь ему в этом определим словарь в виде объекта:


const DICTIONARY = {
  точка: '.',
  запятая: ',',
  вопрос: '?',
  восклицание: '!',
  двоеточие: ':',
  тире: '-',
  абзац: '\n',
  отступ: '\t'
}

Предполагается, что для решение задач, связанных с форматированием, будут использоваться специальные объекты SpeechGrammar и SpeechGrammarList, а также специальный синтаксис JSpeech Grammar Format, однако, в данной статье мы ограничимся обычным объектом.


Вы можете добавить в словарь любые пары ключ/значение, которые посчитаете нужными. Обратите внимание на то, что все ключи в нашем словаре представлены одним словом. Дело в том, что рассматриваемый интерфейс плохо справляется с ключами, которые состоят более чем из одного слова, например, "вопросительный знак", "восклицательный знак" и т.п. Я понимаю, что пара вопрос: '?' не лучшее решение, но для примера сойдет.


Также нам потребуются две функции для редактирования промежуточного и финального результатов:


function editInterim(s) {
  return s
    .split(' ')
    .map((word) => {
      word = word.trim()
      return DICTIONARY[word.toLowerCase()] ? DICTIONARY[word.toLowerCase()] : word
    })
    .join(' ')
}

function editFinal(s) {
  return s.replace(/\s{1,}([\.+,?!:-])/g, '$1')
}

Функция editInterim() разбивает фразу на массив слов, перебирает слова, удаляет пробелы в начале и конце слова и заменяет символом из словаря при совпадении. Обратите внимание, что мы приводим слово в нижний регистр только при поиске совпадения. Если мы сделаем это в строке word = word.trim(), то нивелируем автоматическую "капитализацию" строки, выполняемую браузером.


Функция editFinal() удаляет пробел перед каждым из указанных символов — мы разделяем слова пробелами при объединении в функции editInterim(). Следует отметить, что по умолчанию каждое распознанное слово с двух сторон обрамляется пробелами.


Итак, событие, которое интересует нас больше всего — это result. Реализуем его обработку:


recognition.onresult = (e) => {
  // Промежуточные результаты обновляются на каждом цикле распознавания
  let interim_transcript = ''
  // Перебираем результаты с того места, на котором остановились в прошлый раз
  for (let i = e.resultIndex; i < e.results.length; i++) {
    // Атрибут `isFinal` является индикатором того, что речь закончилась
    if (e.results[i].isFinal) {
      // Редактируем промежуточные результаты
      const result = editInterim(e.results[i][0].transcript)
      // и добавляем их к финальному результату
      final_transcript += result
    } else {
      // В противном случае, записываем распознанные слова в промежуточный результат
      interim_transcript += e.results[i][0].transcript
    }
  }
   // Записываем промежуточные результаты в `input`
  interim_text.value = interim_transcript
  // Редактируем финальный результат
  final_transcript = editFinal(final_transcript)
  // и записываем его в `textarea`
  final_text.value = final_transcript
}

Событие распознавания выглядит следующим образом:


SpeechRecognitionEvent
  bubbles: false
  cancelBubble: false
  cancelable: false
  composed: false
  currentTarget: SpeechRecognition {grammars: SpeechGrammarList, lang: "ru-RU", continuous: true, interimResults: true, maxAlternatives: 3, …}
  defaultPrevented: false
  emma: null
  eventPhase: 0
  interpretation: null
  isTrusted: true
  path: []
  resultIndex: 1
  // здесь нас интересуют только результаты
  results: SpeechRecognitionResultList {0: SpeechRecognitionResult, 1: SpeechRecognitionResult, length: 2}
  returnValue: true
  srcElement: SpeechRecognition {grammars: SpeechGrammarList, lang: "ru-RU", continuous: true, interimResults: true, maxAlternatives: 3, …}
  target: SpeechRecognition {grammars: SpeechGrammarList, lang: "ru-RU", continuous: true, interimResults: true, maxAlternatives: 3, …}
  timeStamp: 59862.61999979615
  type: "result"

Результаты (SpeechRecognitionResultList) выглядят так:


results: SpeechRecognitionResultList
  0: SpeechRecognitionResult
    0: SpeechRecognitionAlternative
      confidence: 0.7990190982818604
      transcript: "привет"
    isFinal: true
    length: 1
  length: 1

Вот почему для получения результата мы обращаемся к e.results[i][0].transcript. На самом деле, поскольку мы указали maxAlternatives = 3, во всех результатах будет представлено по три SpeechRecognitionAlternative. Первым (с индексом 0) всегда будет наиболее подходящий результат с точки зрения браузера.


Все, что нам осталось сделать, это реализовать обработку нажатия кнопок:


buttons.onclick = ({ target }) => {
  switch (target.className) {
    case 'start':
      // Обнуляем переменную для финального результата
      final_transcript = ''
      // Запускаем распознавание
      recognition.start()
      // Устанавливаем индикатор распознавания в значение `true`
      recognizing = true
      // Очищаем `textarea`
      final_text.value = ''
      // Очищаем `input`
      interim_text.value = ''
      break
    case 'stop':
      // Останавливаем распознавание
      recognition.stop()
      // Устанавливаем значение индикатора распознавания в значение `false`
      recognizing = false
      break
    case 'abort':
      // Прекращаем распознавание
      recognition.abort()
      recognizing = false
      break
    case 'copy':
      // Копируем текст из `textarea` в буфер обмена
      navigator.clipboard.writeText(final_text.value)
      // Сообщаем об этом пользователю
      target.textContent = 'Готово'
      const timerId = setTimeout(() => {
        target.textContent = 'Копия'
        clearTimeout(timerId)
      }, 3000)
      break
    case 'clear':
      // Обнуляем переменную для финального результата
      final_transcript = ''
      // Очищаем `textarea`
      final_text.value = ''
      break
    default:
      break
  }
}

Тестируем. Нажимаем на кнопку "Старт", дожидаемся, когда красная точка рядом с фавиконкой перестанет мигать (о готовности к распознаванию можно сообщать пользователю через обработку события speechstart), произносим фразу (произношение должно быть максимально четким и ясным), например, "привет точка как дела вопрос". В input на какое-то время появляется "Привет точка Как дела вопрос", затем этот промежуточный результат редактируется и переносится в textarea в виде "Привет. Как дела?". Отлично, наша машина нас понимает.


Поиграть с кодом можно здесь:



Подумал о том, что было бы неплохо реализовать функцию для удаления последнего распознанного слова на тот случай, если результат распознавания получился некорректным. Для этого потребуется пара удалить: () => removeLastWord() (если добавить эту пару в словарь, то потребуется дополнительная проверка typeof DICTIONARY[word] === 'function') и примерно такая операция:


function removeLastWord() {
  const oldStr = final_text.value
  const newStr = oldStr.substring(0, oldStr.lastIndexOf(' '))
  final_text.value = newStr
}

Однако, добавлять операцию в словарь — плохая идея, для этого лучше использовать отдельный объект, чем мы и займемся в следующем разделе.


Одностраничное приложение с голосовым управлением


Такой вариант использования SpeechRecognition, на мой взгляд, представляет наибольший интерес с точки зрения возможности применения данной технологии в реальных приложениях.


Мы будем генерировать страницы программным способом, поэтому разметка нам не потребуется.


Создаем директорию pages с тремя файлами:


// home.js
export default /*html*/ `
<div id="wrapper">
  <div>Section 1</div>
  <div>Section 2</div>
  <div>Section 3</div>
  <div>Section 4</div>
  <div>Section 5</div>
  <div>Section 6</div>
  <div>Section 7</div>
  <div>Section 8</div>
  <div>Section 9</div>
</div>
`

// product.js
export default /*html*/ `
<h1>This is the Product Page</h1>
`

// about.js
export default /*html*/ `
<h1>This is the About Page</h1>
`

Основной скрипт начинается с импорта страниц и генерации домашней страницы:


import HomePage from './pages/home.js'
import ProductPage from './pages/product.js'
import AboutPage from './pages/about.js'

const { body } = document
body.innerHTML = HomePage

Нам потребуются константы для прокрутки страницы и объект с операциями:


// Константы для прокрутки
const DOWN = 'down'
const UP = 'up'
const RIGHT = 'right'
const LEFT = 'left'

// Операции
const ACTIONS = {
  // операции переключения страниц
  home: () => (body.innerHTML = HomePage),
  product: () => (body.innerHTML = ProductPage),
  about: () => (body.innerHTML = AboutPage),

  // операции прокрутки
  down: () => scroll(DOWN),
  up: () => scroll(UP),
  left: () => scroll(LEFT),
  right: () => scroll(RIGHT),

  // операции переключения цветовой темы
  light: () => body.removeAttribute('class'),
  dark: () => (body.className = 'dark')
}

Далее следуют настройки SpeechRecognition и обработчики событий начала, ошибки и окончания распознавания, аналогичные рассмотренным в предыдущем разделе (кроме настройки языка — для управления страницей мы будем использовать американский английский: recognition.lang = 'en-US').


Обработка события result:


recognition.onresult = (e) => {
  // Перебираем результаты
  for (let i = e.resultIndex; i < e.results.length; i++) {
    if (e.results[i].isFinal) {
      const result = e.results[i][0].transcript.toLowerCase()
      // Выводим слова в консоль, чтобы убедиться в корректности распознавания (как выяснилось, слово `product` очень плохо распознается, возможно, у меня проблемы с его правильным произношением)
      console.log(result)
      // Преобразуем фразу в массив, перебираем слова и выполняем соответствующие операции
      result
        .split(' ')
        .forEach((word) => {
          word = word.trim().toLowerCase()
          // ACTION[word] - это функция
          return ACTIONS[word] ? ACTIONS[word]() : ''
        })
    }
  }
}

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


function scroll(direction) {
  let newPosition
  switch (direction) {
    case DOWN:
      newPosition = scrollY + innerHeight
      break
    case UP:
      newPosition = scrollY - innerHeight
      break
    case RIGHT:
      newPosition = scrollX + innerWidth
      break
    case LEFT:
      newPosition = scrollX - innerWidth
      break
    default:
      break
  }
  if (direction === DOWN || direction === UP) {
    scrollTo({
      top: newPosition,
      behavior: 'smooth'
    })
  } else {
    scrollTo({
      left: newPosition,
      behavior: 'smooth'
    })
  }
}

Наконец, добавляем обработку нажатия клавиш клавиатуры:


window.addEventListener('keydown', (e) => {
  e.preventDefault()
  switch (e.code) {
    // Нажатие пробела запускает распознавание
    case 'Space':
      recognition.start()
      recognizing = true
      break
    // Нажатие `escape` останавливает распознавание
    case 'Escape':
      recognition.stop()
      recognizing = false
    default:
      break
  }
})

Проверяем работоспособность приложения. Нажимаем пробел, дожидаемся готовности браузера к распознаванию речи, произносим следующие фразы:


  • home, product, about — переключение страниц
  • dark, light — переключение цветовой темы
  • down, up, left, right — выполнение прокрутки к соответствующему разделу страницы (имеет видимый эффект только на домашней странице, иногда приходится добавлять слово scroll, например, scroll down)

Поиграть с кодом можно здесь:



Заключение


Как мы видим, WSA существенно расширяет арсенал JavaScript, связанный с добавлением динамики, "оживлением" веб-страниц. Сложно сказать, насколько широкое распространение получит данная технология и насколько активно она будет использоваться. Несмотря на то, что WSA — это технология общего назначения, очевидно, что она внесет серьезный вклад, в первую очередь, в развитие так называемого доступного Интернета.


Мы с вами рассмотрели все основные возможности, предоставляемые WSA. Единственное, что мы не сделали, это не реализовали совместное использование SpeechSynthesis и SpeechRecognition. Первое, что приходит на ум — это голосовой помощник, который реагирует на ключевые слова, встречающиеся во фразе, произнесенной пользователем, отвечая определенными шаблонами и задавая уточняющие вопросы, при необходимости. Более простой вариант: волшебный шар — задаешь любой вопрос, предполагающий однозначный ответ, программа какое-то время "думает" и отвечает да, нет или, например, не знаю, спросите позже. Пусть это будет вашим домашним заданием, все необходимые знания для этого у вас имеются. Обязательно поделитесь результатом в комментариях.




Облачные серверы от Маклауд быстрые и безопасные.


Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!


Теги:webspeechapijavascriptсайтыapivdsvpsбыстрые серверы
Хабы: Блог компании Маклауд Разработка веб-сайтов JavaScript API
Всего голосов 24: ↑22 и ↓2+20
Просмотры3.8K
Комментарии Комментарии 1

Похожие публикации

Лучшие публикации за сутки