Реализация Web Speech API (распознавание/синтез речи) на сайте

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация Web Speech API (распознавание/синтез речи) на сайте
Средняя
~2-3 рабочих дня
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Реализация Web Speech API (распознавание и синтез речи) на сайте

Web Speech API состоит из двух независимых частей: SpeechRecognition (речь → текст) и SpeechSynthesis (текст → речь). Первая нужна для голосового управления интерфейсом, диктовки текста, голосового поиска. Вторая — для озвучивания контента, уведомлений, доступности.

Поддержка браузерами

SpeechRecognition: Chrome/Edge (с webkit-префиксом), Android Chrome. Firefox и Safari — без поддержки. Для продакшена нужен fallback на серверное ASR (Whisper/Deepgram).

SpeechSynthesis: все современные браузеры, включая Safari iOS.

Распознавание речи

const SpeechRecognition =
  window.SpeechRecognition || (window as any).webkitSpeechRecognition

interface UseSpeechRecognitionOptions {
  lang?: string
  continuous?: boolean       // Непрерывная запись vs одна фраза
  interimResults?: boolean   // Промежуточные результаты в реальном времени
  onResult: (transcript: string, isFinal: boolean) => void
  onError?: (error: string) => void
}

function useSpeechRecognition({
  lang = 'ru-RU',
  continuous = false,
  interimResults = true,
  onResult,
  onError,
}: UseSpeechRecognitionOptions) {
  const recognitionRef = useRef<SpeechRecognition | null>(null)
  const [isListening, setIsListening] = useState(false)
  const [isSupported] = useState(() => 'SpeechRecognition' in window || 'webkitSpeechRecognition' in window)

  function start() {
    if (!isSupported) {
      onError?.('Браузер не поддерживает распознавание речи')
      return
    }

    const recognition = new SpeechRecognition()
    recognition.lang = lang
    recognition.continuous = continuous
    recognition.interimResults = interimResults
    recognition.maxAlternatives = 1

    recognition.onstart = () => setIsListening(true)
    recognition.onend = () => setIsListening(false)

    recognition.onresult = (event: SpeechRecognitionEvent) => {
      let finalTranscript = ''
      let interimTranscript = ''

      for (let i = event.resultIndex; i < event.results.length; i++) {
        const transcript = event.results[i][0].transcript
        if (event.results[i].isFinal) {
          finalTranscript += transcript
        } else {
          interimTranscript += transcript
        }
      }

      if (finalTranscript) {
        onResult(finalTranscript.trim(), true)
      } else if (interimTranscript) {
        onResult(interimTranscript.trim(), false)
      }
    }

    recognition.onerror = (event: SpeechRecognitionErrorEvent) => {
      const messages: Record<string, string> = {
        'not-allowed': 'Доступ к микрофону запрещён',
        'no-speech': 'Речь не обнаружена',
        'network': 'Ошибка сети при распознавании',
        'audio-capture': 'Микрофон недоступен',
      }
      onError?.(messages[event.error] ?? event.error)
      setIsListening(false)
    }

    recognitionRef.current = recognition
    recognition.start()
  }

  function stop() {
    recognitionRef.current?.stop()
    recognitionRef.current = null
  }

  return { isListening, isSupported, start, stop }
}

Компонент диктовки текста

function VoiceDictation({ onChange }: { onChange: (text: string) => void }) {
  const [transcript, setTranscript] = useState('')
  const [interim, setInterim] = useState('')

  const { isListening, isSupported, start, stop } = useSpeechRecognition({
    lang: 'ru-RU',
    continuous: true,
    interimResults: true,
    onResult: (text, isFinal) => {
      if (isFinal) {
        setTranscript((prev) => {
          const next = prev + (prev ? ' ' : '') + text
          onChange(next)
          return next
        })
        setInterim('')
      } else {
        setInterim(text)
      }
    },
    onError: (err) => console.warn('Speech error:', err),
  })

  if (!isSupported) {
    return <p className="text-sm text-gray-500">Голосовой ввод недоступен в этом браузере</p>
  }

  return (
    <div className="border rounded-lg p-3">
      <div className="min-h-[80px] text-sm">
        <span>{transcript}</span>
        {interim && <span className="text-gray-400 italic"> {interim}</span>}
      </div>
      <div className="flex gap-2 mt-2 border-t pt-2">
        <button
          onClick={isListening ? stop : start}
          className={`flex items-center gap-2 px-3 py-1.5 rounded text-sm ${
            isListening
              ? 'bg-red-100 text-red-700'
              : 'bg-blue-100 text-blue-700'
          }`}
        >
          {isListening ? (
            <>
              <span className="w-2 h-2 bg-red-500 rounded-full animate-pulse" />
              Стоп
            </>
          ) : (
            'Говорите'
          )}
        </button>
        <button
          onClick={() => { setTranscript(''); setInterim(''); onChange('') }}
          className="text-sm text-gray-500 hover:text-gray-700"
        >
          Очистить
        </button>
      </div>
    </div>
  )
}

Голосовые команды

function useVoiceCommands(commands: Record<string, () => void>) {
  const { start, stop, isListening } = useSpeechRecognition({
    lang: 'ru-RU',
    continuous: true,
    interimResults: false,
    onResult: (transcript) => {
      const lower = transcript.toLowerCase().trim()
      for (const [phrase, action] of Object.entries(commands)) {
        if (lower.includes(phrase)) {
          action()
          break
        }
      }
    },
  })

  return { start, stop, isListening }
}

// Использование
const { start } = useVoiceCommands({
  'следующий слайд': () => goToSlide(current + 1),
  'предыдущий слайд': () => goToSlide(current - 1),
  'первый слайд': () => goToSlide(0),
  'полный экран': () => document.documentElement.requestFullscreen(),
})

Синтез речи (Text-to-Speech)

class TextToSpeech {
  private synth = window.speechSynthesis
  private currentUtterance: SpeechSynthesisUtterance | null = null

  speak(text: string, options: {
    lang?: string
    rate?: number    // 0.1–10, по умолчанию 1
    pitch?: number   // 0–2, по умолчанию 1
    volume?: number  // 0–1
    voiceName?: string
    onEnd?: () => void
  } = {}) {
    this.stop()

    const utterance = new SpeechSynthesisUtterance(text)
    utterance.lang = options.lang ?? 'ru-RU'
    utterance.rate = options.rate ?? 1
    utterance.pitch = options.pitch ?? 1
    utterance.volume = options.volume ?? 1

    if (options.voiceName) {
      const voices = this.synth.getVoices()
      const voice = voices.find((v) => v.name === options.voiceName)
      if (voice) utterance.voice = voice
    }

    if (options.onEnd) utterance.onend = options.onEnd

    // Workaround для Chrome: длинный текст обрезается через ~15 секунд
    utterance.onboundary = (event) => {
      if (event.name === 'sentence') {
        // Периодически "будим" синтезатор
        this.synth.pause()
        this.synth.resume()
      }
    }

    this.currentUtterance = utterance
    this.synth.speak(utterance)
  }

  stop() {
    this.synth.cancel()
    this.currentUtterance = null
  }

  pause() { this.synth.pause() }
  resume() { this.synth.resume() }

  getVoices(): SpeechSynthesisVoice[] {
    return this.synth.getVoices().filter((v) => v.lang.startsWith('ru'))
  }
}

Fallback: Whisper API для серьёзного ASR

Когда браузерного ASR недостаточно (низкое качество, нет поддержки Firefox/Safari):

async function transcribeWithWhisper(audioBlob: Blob): Promise<string> {
  const formData = new FormData()
  formData.append('file', audioBlob, 'audio.webm')
  formData.append('model', 'whisper-1')
  formData.append('language', 'ru')

  const response = await fetch('https://api.openai.com/v1/audio/transcriptions', {
    method: 'POST',
    headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}` },
    body: formData,
  })

  const data = await response.json()
  return data.text
}

Запись через MediaRecorder API, отправка на /api/transcribe, который проксирует в Whisper — так ключ не утекает на фронт.

Что делаем

Определяем сценарий: голосовой поиск, диктовка, команды управления, TTS для accessibility. Реализуем соответствующую часть API, добавляем fallback (Whisper для ASR, браузерный TTS везде работает без замены). Тестируем на различных браузерах, учитываем политику автоплея.

Срок: голосовой поиск или диктовка — 1–2 дня. Голосовые команды + TTS — 2–3 дня. С Whisper fallback — плюс 1 день.