При розробці голосового асистента для 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 тижні підтримки після здачі. Зв'яжіться з нами для консультації з інтеграції ElevenLabs у ваш мобільний додаток — оцінимо ваш проєкт безкоштовно. Замовте інтеграцію з гарантією термінів та отримайте готове рішення без типових помилок.







