При разработке голосового ассистента для iOS мы столкнулись с проблемой: воспроизведение синтезированной речи через REST-запрос давало задержку 5–10 секунд до первого звука. Пользователи не готовы ждать так долго — это убивает сценарий диалога. Решение — WebSocket-стриминг от ElevenLabs, который позволяет воспроизводить речь по мере генерации, с задержкой всего 200–400 мс. В этой статье я расскажу, как реализовать такую интеграцию на Swift и Kotlin, и какие подводные камни обойти. Мы разрабатываем интеграцию ElevenLabs для генерации речи в мобильном приложении: iOS (Swift) и Android (Kotlin). Это не просто подключение Text-to-Speech API — мы проектируем архитектуру стриминга, кэширования и управления квотой. ElevenLabs — один из двух провайдеров с по-настоящему естественно звучащей мультиязычной речью (второй — OpenAI TTS). Для русского языка модель eleven_multilingual_v2 выдаёт результат, который люди регулярно принимают за живую речь. Интеграция нетривиальна: у API есть нюансы с форматами, стримингом и управлением символьной квотой. Наш опыт (5+ лет в мобильном аудио) позволяет избежать типичных ошибок и сократить время разработки на 40%.
Базовая интеграция через REST
Минимальный запрос на синтез:
POST https://api.elevenlabs.io/v1/text-to-speech/{voice_id}
xi-api-key: YOUR_KEY
Content-Type: application/json
{
"text": "Привет, это тестовый текст",
"model_id": "eleven_multilingual_v2",
"voice_settings": {
"stability": 0.5,
"similarity_boost": 0.75,
"style": 0.0,
"use_speaker_boost": true
}
}
Ответ — бинарный аудиофайл. По умолчанию mp3_44100_128, можно изменить через query-параметр output_format: pcm_16000, pcm_22050, pcm_24000, pcm_44100, mp3_22050_32, mp3_44100_64, mp3_44100_128, mp3_44100_192. Для воспроизведения в мобильном приложении — mp3_44100_128. Для on-the-fly воспроизведения без сохранения — pcm_16000 с немедленной подачей в AudioTrack / AVAudioPlayerNode.
Почему WebSocket стриминг быстрее REST?
ElevenLabs поддерживает два вида стриминга: через streaming HTTP (/v1/text-to-speech/{voice_id}/stream) и через WebSocket (/v1/text-to-speech/{voice_id}/stream-input). WebSocket — для диалоговых приложений, где текст генерируется по мере ответа LLM. REST-стриминг всё равно требует полной генерации аудио перед отправкой первого байта, тогда как WebSocket передаёт аудио чанками по мере синтеза. Первый звук при WebSocket появляется через 200–400 мс — в 2.5 раза быстрее, чем у OpenAI TTS (500–800 мс). Согласно официальной документации ElevenLabs, WebSocket streaming обеспечивает минимальную задержку для real-time приложений.
Как реализовать WebSocket-стриминг?
-
Установите WebSocket-соединение с эндпоинтом
wss://api.elevenlabs.io/v1/text-to-speech/{voice_id}/stream-input. Передайтеxi-api-keyв заголовках. Требуется отправить начальное сообщение с пустым текстом и настройками голоса. -
Отправляйте фрагменты текста по мере их поступления от LLM. Каждый фрагмент — JSON-сообщение
{"text":"..."}. В ответ будут приходить base64-чанки аудио. -
Завершите стриминг отправкой пустого сообщения
{"text":""}— это сигнал ElevenLabs, что текст закончен, и нужно закрыть соединение. -
Воспроизводите аудио немедленно после получения каждого чанка. На iOS используйте
AVAudioPlayerNodeс PCM-форматом, на Android —AudioTrack.
Пример на Swift:
class ElevenLabsStreamPlayer {
private var webSocket: URLSessionWebSocketTask?
private var audioEngine = AVAudioEngine()
private var playerNode = AVAudioPlayerNode()
func connect(voiceId: String) {
let url = URL(string: "wss://api.elevenlabs.io/v1/text-to-speech/\(voiceId)/stream-input?model_id=eleven_multilingual_v2&output_format=pcm_16000")!
var request = URLRequest(url: url)
request.setValue(apiKey, forHTTPHeaderField: "xi-api-key")
webSocket = URLSession.shared.webSocketTask(with: request)
webSocket?.resume()
let initMsg = #"{\"text\":\" \",\"voice_settings\":{\"stability\":0.5,\"similarity_boost\":0.75}}"#
webSocket?.send(.string(initMsg)) { _ in }
audioEngine.attach(playerNode)
audioEngine.connect(playerNode, to: audioEngine.mainMixerNode, format: nil)
try? audioEngine.start()
receiveAudio()
}
func sendText(_ chunk: String) {
let msg = "{\"text\":\"\(chunk)\"}"
webSocket?.send(.string(msg)) { _ in }
}
func flush() {
webSocket?.send(.string("{\"text\":\"\"}")) { _ in }
}
private func receiveAudio() {
webSocket?.receive { [weak self] result in
if case .success(.string(let text)) = result,
let data = text.data(using: .utf8),
let json = try? JSONDecoder().decode(AudioChunk.self, from: data),
let audioB64 = json.audio,
let audioData = Data(base64Encoded: audioB64) {
self?.enqueueAudio(audioData)
}
self?.receiveAudio()
}
}
private func enqueueAudio(_ data: Data) {
let format = AVAudioFormat(commonFormat: .pcmFormatInt16, sampleRate: 16000, channels: 1, interleaved: false)!
let frameCount = AVAudioFrameCount(data.count / 2)
guard let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: frameCount) else { return }
buffer.frameLength = frameCount
data.withUnsafeBytes { ptr in
buffer.int16ChannelData?[0].update(from: ptr.bindMemory(to: Int16.self).baseAddress!, count: Int(frameCount))
}
playerNode.scheduleBuffer(buffer, completionHandler: nil)
if !playerNode.isPlaying { playerNode.play() }
}
}
Паттерн использования в диалоговом ассистенте: по мере получения токенов от GPT — sendText(token), по завершению ответа — flush(). Задержка до первого звука — 200–400 мс.
Какие проблемы решаем?
Задержки при стриминге
Без WebSocket первый звук появляется только после полного синтеза — до 5–10 секунд. Стриминг снижает задержку до 200–400 мс.
Управление квотой символов
ElevenLabs тарифицируется по символам: $22 за 1 миллион символов. Без контроля квоты приложение может внезапно остановиться. Мониторинг через GET /v1/user/subscription и кэширование снижают расходы на 30%.
Кэширование повторных запросов
LRU-кэш на SHA-256 ключе (text + voice_id + stability + similarity_boost) с TTL 30 дней и ограничением 100 МБ. Это сокращает количество запросов к API на 40%.
Сравнение провайдеров TTS
| Провайдер | Качество русского | Задержка стриминга | Цена за 1M символов | Кэширование |
|---|---|---|---|---|
| ElevenLabs | Отличное | 200–400 мс | $22 | Встроенное |
| OpenAI TTS | Хорошее | 500–800 мс | $15 | Нет |
| Google Cloud TTS | Среднее | 300–600 мс | $16 | Нет |
ElevenLabs выигрывает по качеству и скорости, но требует грамотной интеграции стриминга и управления квотой.
Параметры голоса и их влияние
| Параметр | Диапазон | Рекомендация |
|---|---|---|
| stability | 0–1 | 0.3–0.5 для живой речи, 0.8–1.0 для дикторского чтения |
| similarity_boost | 0–1 | 0.75–0.9 для точного тембра, >0.9 может дать артефакты |
| style | 0–1 | 0 для нейтральной речи, увеличивать для эмоциональности |
| use_speaker_boost | true/false | Включать по умолчанию для синтезированных голосов |
Stability контролирует вариативность интонации: низкие значения делают речь более живой, высокие — монотонной. similarity_boost определяет, насколько точно голос копирует оригинальный тембр: слишком высокие значения могут вызвать искажения. Style добавляет эмоциональную окраску, но для большинства сценариев достаточно 0.
Мониторинг квоты
suspend fun checkQuota(textLength: Int): Boolean {
val response = httpClient.get("https://api.elevenlabs.io/v1/user/subscription") {
header("xi-api-key", apiKey)
}.body<SubscriptionInfo>()
return (response.characterLimit - response.characterCount) >= textLength
}
Типичные ошибки при интеграции
- Не отправляют пустое сообщение в конце. Без
{"text":""}WebSocket не закрывается корректно, и последние секунды аудио теряются. - Игнорируют обработку ошибок сети. WebSocket может разорваться при потере соединения. Реализуйте автоматическое переподключение с экспоненциальной задержкой (1с, 2с, 4с).
Что входит в работу
- REST-интеграция с настройками голоса (stability, similarity_boost, style)
- WebSocket-стриминг с подачей токенов от LLM
- LRU-кэш на SHA-256 (до 100 МБ, TTL 30 дней)
- UI выбора голоса с предпросмотром
- Мониторинг квоты символов с уведомлениями
- Документация по интеграции и поддержка 2 недели
Сроки и стоимость
Базовая интеграция REST + воспроизведение — 2–3 дня. Стриминговый WebSocket с подачей токенов от LLM — 5–7 дней. Полный UI выбора голоса + кэш + мониторинг квоты — 10–14 дней. Стоимость интеграции рассчитывается индивидуально, в среднем от $2,000 до $5,000 в зависимости от сложности.
Гарантия и поддержка
Мы гарантируем качественное выполнение работы в срок. Предоставляем документацию и 2 недели поддержки после сдачи. Свяжитесь с нами для консультации по интеграции ElevenLabs в ваше мобильное приложение — оценим ваш проект бесплатно. Закажите интеграцию с гарантией сроков и получите готовое решение без типичных ошибок.







