Integrar serviços de síntese de voz em tempo real (Text-to-Speech ou TTS) em aplicações web modernas exige conciliar latência de resposta, expressividade acústica e segurança rigorosa de credenciais. A Listnr resolve essa demanda por meio de uma arquitetura em duas camadas: uma interface REST V1 multiprovedor que reúne vozes de Microsoft Azure, Google Cloud, AWS Polly e IBM Watson, e um mecanismo de streaming V2 de baixa latência focado em vozes Ultra Premium e clonadas.
Para transmitir voz sintetizada em navegadores sem expor tokens confidenciais, a prática recomendada consiste em implementar um proxy no backend. Essa camada recebe as solicitações da interface, injeta o token de autenticação, consome o endpoint /stream-voice da Listnr e encaminha os blocos binários de áudio diretamente para o cliente via streaming.
sequenceDiagram
autonumber
actor User as Navegador Web (Cliente)
participant Server as Backend da Aplicação (Proxy)
participant Listnr as API de Streaming Listnr V2
User->>Server: POST /api/tts (texto, voiceId, configurações)
Note over Server: Insere x-listnr-token com segurança
Server->>Listnr: POST /api/v2/stream-voice
Listnr-->>Server: Fluxo binário fragmentado (WAV / MP3)
Server-->>User: Streaming repassado (Transfer-Encoding: chunked)
Note over User: Web Audio API / HTML5 Audio inicia reprodução imediata
1. Autenticação e Princípios de Arquitetura
Aplicações web no frontend nunca devem disparar chamadas diretas para a Listnr contendo credenciais de produção. A autenticação é realizada por meio do cabeçalho proprietário x-listnr-token, diretamente associado ao saldo de palavras e ao plano de faturamento da conta. A exposição dessa chave no código-fonte do cliente compromete o controle de custos e pode acarretar esgotamento indevido da cota.
Isolamento de Credenciais e Proxy no Backend
O padrão de arquitetura consolidado consiste em posicionar um gateway intermediário no servidor (como rotas de API do Next.js, Express, Fastify ou FastAPI) entre o navegador e a infraestrutura da Listnr.
| Componente da API | Host / Endpoint | Função Principal | Requisitos de Segurança |
|---|---|---|---|
| Streaming V2 | https://cloning.listnr.tech/api/v2/stream-voice | Fluxo binário de baixa latência para vozes Ultra Premium e clones | Apenas no backend; exige cabeçalho x-listnr-token |
| Listagem de Vozes V2 | https://cloning.listnr.tech/api/v2/available-voices | Relação de identificadores, modelos prontos e clones da conta | Cache no servidor; exposto via endpoint interno |
| REST V1 Multiprovedor | https://bff.listnr.tech/api/tts/v1/convert-text | Conversão em lote para vozes de Azure, Google, AWS e IBM | Uso em backend; aceita documentos SSML completos |
2. Descoberta de Vozes: Obtendo o Voice ID
Antes de iniciar a síntese, a aplicação precisa obter um identificador válido de modelo de voz (voice_id). Essa lista pode ser consultada na inicialização do serviço e armazenada em memória ou cache distribuído como o Redis.
# Consultar vozes padrão e clonadas vinculadas à conta
curl -X GET "https://cloning.listnr.tech/api/v2/available-voices" \
-H "x-listnr-token: SUA_API_KEY_LISTNR" \
-H "Accept: application/json"
O retorno apresenta uma lista de objetos contendo identificadores alfanuméricos únicos (como 21m00Tcm4TlvDq8ikWAM). O frontend envia esse identificador para o backend para direcionar a síntese ao perfil sonoro desejado.
3. Síntese em Streaming e Ajuste Fino de Parâmetros
O endpoint de streaming V2 recebe um payload JSON composto pelo texto a ser sintetizado, o identificador de voz e o objeto settings, responsável pela calibração vocal.
{
"voice_id": "21m00Tcm4TlvDq8ikWAM",
"text": "Aviso do sistema: A sincronização do banco apresentou atraso.<break time=\"0.4s\"/><break strength=\"strong\"/>Iniciando transição de contingência.",
"settings": {
"stability": 0.71,
"similarity_boost": 0.50,
"style": 0.05,
"use_speaker_boost": false
}
}
Guia de Calibração dos Parâmetros
Compreender o impacto de cada parâmetro assegura um equilíbrio preciso entre estabilidade e naturalidade:
stability(Ponto flutuante, de0.0a1.0): Controla a constância e a variabilidade expressiva da fala. Valores mais baixos (como0.25a0.45) conferem maior dinamismo emocional e cadência flexível, ideais para assistentes de conversação e narrativas. Valores elevados (como0.70a0.85) produzem locuções estáveis e sóbrias, recomendadas para avisos institucionais, sistemas de URA (IVR) e leitura técnica.similarity_boost(Ponto flutuante, de0.0a1.0): Define o nível de aderência ao timbre da voz original. Recomenda-se manter o intervalo entre0.50e0.75; valores muito próximos a1.0podem reproduzir ruídos de fundo e imperfeições presentes na gravação de referência.style(Ponto flutuante, de0.0a1.0): Enfatiza maneirismos e modulações expressivas da voz. Valores acima de0.20tornam a entonação mais viva, mas exigem maior capacidade computacional, elevando sutilmente o tempo até o primeiro byte (TTFB).use_speaker_boost(Booleano): Habilita um pós-processamento acústico para realçar o brilho e a presença da voz. Em aplicações web com requisitos estritos de latência, mantenha o valor comofalsepara otimizar o tempo de resposta.
Pausas e Ritmo com Tags SSML
No fluxo do V2 /stream-voice, não há necessidade de envolver o conteúdo em tags formais <speak>. As pausas podem ser inseridas diretamente no corpo do texto:
<break time="300ms"/>ou<break time="1.2s"/>: Insere pausas com tempo exato, permitindo sincronização com animações da interface.<break strength="x-weak" | "weak" | "medium" | "strong" | "x-strong"/>: Cria respirações e quebras de frase com base na cadência gramatical natural.
4. Implementação Completa para Aplicações Web
Abaixo está uma estrutura completa e pronta para produção: um proxy de streaming desenvolvido em Node.js/Express e um cliente web que consome e reproduz o áudio recebido.
Proxy de Streaming no Backend (server.js)
import express from 'express';
import fetch from 'node-fetch';
const app = express();
app.use(express.json());
const LISTNR_API_KEY = process.env.LISTNR_API_KEY;
const STREAM_URL = 'https://cloning.listnr.tech/api/v2/stream-voice';
app.post('/api/tts/stream', async (req, res) => {
try {
const { text, voiceId, settings } = req.body;
if (!text || !voiceId) {
return res.status(400).json({ error: 'Parâmetros text ou voiceId ausentes.' });
}
const payload = {
voice_id: voiceId,
text: text,
settings: {
stability: settings?.stability ?? 0.71,
similarity_boost: settings?.similarity_boost ?? 0.50,
style: settings?.style ?? 0.0,
use_speaker_boost: settings?.use_speaker_boost ?? false
}
};
const upstreamResponse = await fetch(STREAM_URL, {
method: 'POST',
headers: {
'x-listnr-token': LISTNR_API_KEY,
'Content-Type': 'application/json',
'Accept': '*/*'
},
body: JSON.stringify(payload)
});
if (!upstreamResponse.ok) {
const errorText = await upstreamResponse.text();
return res.status(upstreamResponse.status).json({
error: `Falha na API Listnr: ${upstreamResponse.statusText}`,
details: errorText
});
}
// Configura cabeçalhos para repasse em streaming
res.setHeader('Content-Type', 'audio/wav');
res.setHeader('Transfer-Encoding', 'chunked');
// Conecta o fluxo da Listnr diretamente à resposta do cliente
upstreamResponse.body.pipe(res);
} catch (err) {
console.error('Erro no streaming de TTS:', err);
res.status(500).json({ error: 'Falha interna no servidor proxy.' });
}
});
app.listen(3000, () => console.log('Proxy de áudio rodando na porta 3000'));
Reprodução no Navegador (client.js)
/**
* Solicita o fluxo de áudio ao servidor e inicia a reprodução imediatamente
*/
async function streamAndPlayAudio(text, voiceId) {
const response = await fetch('/api/tts/stream', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: text,
voiceId: voiceId,
settings: {
stability: 0.65,
similarity_boost: 0.60,
style: 0.0
}
})
});
if (!response.ok) {
throw new Error(`Falha ao obter áudio: ${response.status}`);
}
// Converte os blocos binários em Blob e inicia via HTML5 Audio
const audioBlob = await response.blob();
const audioUrl = URL.createObjectURL(audioBlob);
const audio = new Audio(audioUrl);
audio.onended = () => URL.revokeObjectURL(audioUrl);
await audio.play();
}
5. Recomendações e Decisões de Arquitetura
Ao implantar a solução em escala, considere os seguintes pontos técnicos:
- Escolha entre REST V1 e Streaming V2: Opte pelo V1 caso seu fluxo exija vozes consolidadas da Microsoft ou Google, ou demande marcações temporais por palavra (Word Timestamps) para sincronia de legendas. Prefira o Streaming V2 quando o requisito for conversação dinâmica com mínima latência inicial.
- Controle de Cota e Debounce de Requisições: Como o consumo é debitado por caractere sintetizado, implemente debounce no front-end para evitar chamadas duplicadas durante digitação ou transições de tela, cancelando requisições em andamento com
AbortController. - Cache de Áudios Estáticos na Borda: Mensagens fixas do sistema, saudações e instruções recorrentes devem ser sintetizadas uma única vez e armazenadas em um bucket S3 com distribuição por CDN, preservando o streaming dinâmico exclusivamente para conteúdos gerados sob demanda.