Розробка поіску з голосовим вводом у мобільному додатку
Голосовий поіск — це не просто кнопка з мікрофоном. Це ланцюжок: захват аудіо, відправлення на розпізнавання, отримання тексту, формування поскового запроса. Якщо хоч одне звено працює з затримкою або помилкою, користувач закриває додаток. На практиці основні проблеми лежать саме у правильній інтеграції Speech Recognition API, а не у UI.
Де частіше за все ломається реалізація
iOS: неправильна робота з SFSpeechRecognizer
Найчастіша помилка — запускати SFSpeechRecognitionTask без попередньої перевірки authorizationStatus. Додаток мовчить, користувач думає, що кнопка сломана. Друга проблема: розробники використовують SFSpeechURLRecognitionRequest (файловий варіант) замість SFSpeechAudioBufferRecognitionRequest для live-вводу. Результат — користувач говорить, чекає, а транскрипція з'являється тільки після зупинки запису.
Правильний підхід: AVAudioEngine + SFSpeechAudioBufferRecognitionRequest з shouldReportPartialResults = true. Це дає часткові результати по мере мови — ровно те, що користувач бачить у системному Siri.
let request = SFSpeechAudioBufferRecognitionRequest()
request.shouldReportPartialResults = true
recognitionTask = speechRecognizer.recognitionTask(with: request) { result, error in
guard let result else { return }
self.searchBar.text = result.bestTranscription.formattedString
if result.isFinal {
self.submitSearch(query: result.bestTranscription.formattedString)
}
}
let inputNode = audioEngine.inputNode
let format = inputNode.outputFormat(forBus: 0)
inputNode.installTap(onBus: 0, bufferSize: 1024, format: format) { buffer, _ in
request.append(buffer)
}
audioEngine.prepare()
try audioEngine.start()
Android: вибір між SpeechRecognizer та RecognizerIntent
RecognizerIntent запускає системний діалог розпізнавання — швидко інтегрується, але виглядає чужеродно в UI додатку та не підтримує EXTRA_PARTIAL_RESULTS на всіх пристроях. SpeechRecognizer дає повний контроль, але потребує акуратної обробки життєвого циклу: потрібно викликати destroy() в onDestroy(), інакше отримуємо утечку через RecognitionListener.
Для inline-інтеграції без системного попапу:
val recognizer = SpeechRecognizer.createSpeechRecognizer(context)
val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply {
putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM)
putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true)
putExtra(RecognizerIntent.EXTRA_LANGUAGE, "ru-RU")
}
recognizer.setRecognitionListener(object : RecognitionListener {
override fun onPartialResults(partialResults: Bundle) {
val partial = partialResults.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION)
searchInput.setText(partial?.firstOrNull() ?: "")
}
override fun onResults(results: Bundle) {
val text = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION)?.firstOrNull()
text?.let { submitSearch(it) }
}
// ... остальні коллбеки
})
recognizer.startListening(intent)
Flutter: speech_to_text vs прямі Platform Channels
Пакет speech_to_text закриває 90% завдань. Проблема виникає в багатомовних додатках: localeId потрібно передавати явно, інакше на Android розпізнавання йде на мові системи, а не додатку.
Як ми це робимо
Для більшості додатків нативний Speech API достатній та не потребує зовнішніх залежностей. Якщо потрібна більш висока точність або підтримка специфічної термінології (медицина, юридичні терміни, технічні назви) — підключаємо Google Cloud Speech-to-Text або Whisper API від OpenAI: вони дають можливість дообучити модель на предметно-специфічному словарі через SpeechContext (Google) або system prompt (Whisper).
Важливий момент, який часто упускають: анімація рівня звуку під час запису. Вона не декоративна — без візуальної зворотнього зв'язку користувач не розуміє, слухає його додаток. На iOS рівень беремо з AVAudioRecorder.averagePower(forChannel:), на Android — з MediaRecorder.getMaxAmplitude().
Поисковий запрос після транскрипції проходить нормалізацію: убираємо слова-паразити, приводимо до нижнього регістру, обробляємо опечатки транскрипції — «айфон» та «iphone» повинні давати однаковий результат.
Процес роботи
Аналіз вимог: мови, тип контенту (вільна мова чи команди), потрібна ли offline-підтримка.
Реалізація: запрос дозволів, інтеграція Speech API, обробка часткових результатів, анімація мікрофона.
Нормалізація запроса та інтеграція з поісковим бекендом.
Тестування на реальних пристроях з різними акцентами та в шумних умовах.
Орієнтири за строками
Базова інтеграція нативного Speech API з UI — 2–3 дні. Якщо потрібна мультимовна підтримка, offline-режим через локальну модель або інтеграція з хмарним ASR — 1–1,5 тижня.







