Resumo
As interfaces web evoluíram além de cliques e pressionamentos de tecla. A API Web Speech, desenvolvida pelo W3C, fornece dois subsistemas independentes para integrar voz em qualquer aplicação web: SpeechRecognition (speech-to-text) para capturar e transcrever a voz do usuário, e SpeechSynthesis (text-to-speech) para ler texto em voz alta usando as vozes disponíveis no sistema.
Este artigo cobre a arquitetura de ambos os subsistemas, mostra implementações práticas com código e aborda problemas críticos como a fragmentação entre navegadores, o bug dos 15 segundos no Chrome e as restrições do iOS.
1. Dois Subsistemas Independentes
A API é dividida em dois módulos completamente separados:
| Subsistema | Direção | Interface | Suporte |
|---|---|---|---|
| SpeechRecognition | Usuário → App (entrada de microfone) | SpeechRecognition / webkitSpeechRecognition | Limitado (principalmente Chromium) |
| SpeechSynthesis | App → Usuário (saída de áudio) | window.speechSynthesis + SpeechSynthesisUtterance | Universal (Baseline) |
Nota: SpeechSynthesis tem adoção universal em todos os navegadores modernos. SpeechRecognition, no entanto, continua sendo experimental com suporte fragmentado. Sempre implemente detecção de recursos antes de usar qualquer um dos dois.
2. Reconhecimento de Voz (Speech-to-Text)
2.1 Detecção de Suporte e Instanciação
Devido ao uso de prefixos de vendors, o construtor deve ser resolvido de forma defensiva:
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SpeechRecognition) {
console.error('SpeechRecognition não é suportado neste navegador.');
}
2.2 Propriedades de Configuração
Uma vez instanciado, o motor de reconhecimento é configurado através de quatro propriedades chave:
| Propriedade | Tipo | Descrição |
|---|---|---|
lang | string | Tag BCP 47 do idioma (ex.: "en-US", "pt-BR"). Por padrão, usa o atributo lang do documento. |
continuous | boolean | Se true, continua ouvindo após retornar resultados. Por padrão: false (para após um resultado). |
interimResults | boolean | Se true, emite transcrições provisórias em tempo real antes da finalização. |
maxAlternatives | number | Número de transcrições alternativas por segmento de resultado. Por padrão: 1. |
const recognition = new SpeechRecognition();
recognition.lang = 'pt-BR';
recognition.continuous = true;
recognition.interimResults = true;
recognition.maxAlternatives = 1;
2.3 A Hierarquia de Resultados
Os resultados não são strings puras. Chegam como uma árvore de objetos com três níveis:
SpeechRecognitionResultList— uma coleção tipo lista acessível viaevent.resultsSpeechRecognitionResult— cada elemento contém um booleanoisFinale uma ou mais alternativasSpeechRecognitionAlternative— contém a stringtranscripte um valor deconfidence(0 a 1)
recognition.onresult = (event) => {
for (let i = event.resultIndex; i < event.results.length; i++) {
const result = event.results[i];
const transcript = result[0].transcript;
const confidence = result[0].confidence;
if (result.isFinal) {
console.log(`Final: "${transcript}" (${(confidence * 100).toFixed(1)}%)`);
} else {
console.log(`Provisório: "${transcript}"`);
}
}
};
2.4 Ciclo de Vida de Eventos
O motor de reconhecimento dispara eventos em uma sequência determinística:
start → audiostart → soundstart → speechstart
↓
result (se repete enquanto se fala)
↓
speechend → soundend → audioend → end
Eventos chave a manipular:
recognition.onspeechstart = () => {
console.log('Voz detectada — transcrição em andamento...');
};
recognition.onerror = (event) => {
switch (event.error) {
case 'not-allowed':
console.error('Acesso ao microfone negado pelo usuário ou política.');
break;
case 'network':
console.error('Serviço de reconhecimento na nuvem inalcançável.');
break;
case 'no-speech':
console.warn('Nenhuma voz detectada — apenas silêncio ou ruído de fundo.');
break;
case 'aborted':
console.warn('O reconhecimento foi abortado.');
break;
}
};
recognition.onnomatch = () => {
console.warn('Fala detectada, mas não pôde ser reconhecida.');
};
recognition.onend = () => {
console.log('Sessão de reconhecimento finalizada.');
};
2.5 Exemplo Completo: Ditado ao Vivo
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SpeechRecognition) {
document.getElementById('status').textContent = 'Reconhecimento de voz não suportado.';
} else {
const recognition = new SpeechRecognition();
recognition.lang = 'pt-BR';
recognition.continuous = true;
recognition.interimResults = true;
const finalOutput = document.getElementById('final');
const interimOutput = document.getElementById('interim');
let finalTranscript = '';
recognition.onresult = (event) => {
let interim = '';
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 {
interim += transcript;
}
}
finalOutput.textContent = finalTranscript;
interimOutput.textContent = interim;
};
recognition.onerror = (event) => {
document.getElementById('status').textContent = `Erro: ${event.error}`;
};
document.getElementById('start-btn').addEventListener('click', () => {
finalTranscript = '';
recognition.start();
document.getElementById('status').textContent = 'Ouvindo...';
});
document.getElementById('stop-btn').addEventListener('click', () => {
recognition.stop();
document.getElementById('status').textContent = 'Parado.';
});
}
3. Síntese de Voz (Text-to-Speech)
3.1 O Controlador Singleton
Diferente do SpeechRecognition, o motor de síntese é acessado através de uma única instância global:
const synth = window.speechSynthesis;
Este controlador gerencia uma fila FIFO de locuções e expõe três flags de estado somente leitura:
| Propriedade | Descrição |
|---|---|
synth.pending | true se houver locuções aguardando na fila |
synth.speaking | true se o áudio estiver sendo produzido atualmente |
synth.paused | true se a reprodução tiver sido pausada |
Métodos de controle:
synth.speak(utterance);
synth.pause();
synth.resume();
synth.cancel();
3.2 Criando Locuções
O objeto SpeechSynthesisUtterance é a unidade de fala. Contém o texto e toda a configuração prosódica:
| Propriedade | Intervalo | Descrição |
|---|---|---|
text | String | O conteúdo de texto a ser pronunciado |
voice | SpeechSynthesisVoice | O perfil de voz a ser utilizado |
lang | String (BCP 47) | Sobrescrita do idioma |
pitch | 0 – 2 | Tom de voz (1 = normal) |
rate | 0.1 – 10 | Velocidade da fala (1 = normal) |
volume | 0 – 1 | Volume do áudio (1 = máximo) |
const utterance = new SpeechSynthesisUtterance('Olá, bem-vindo à aplicação.');
utterance.lang = 'pt-BR';
utterance.pitch = 1;
utterance.rate = 1;
utterance.volume = 0.8;
synth.speak(utterance);
3.3 Carregando Vozes de Forma Assíncrona
As vozes são carregadas assincronamente pelo navegador (especialmente no Chromium). Uma chamada síncrona para getVoices() durante o carregamento da página frequentemente retorna um array vazio. Você deve ouvir o evento voiceschanged:
const synth = window.speechSynthesis;
let voices = [];
const loadVoices = () => {
voices = synth.getVoices();
console.log(`${voices.length} vozes carregadas.`);
};
loadVoices();
if (synth.onvoiceschanged !== undefined) {
synth.onvoiceschanged = loadVoices;
}
Cada objeto SpeechSynthesisVoice contém:
voices.forEach((voice) => {
console.log(`${voice.name} (${voice.lang}) ${voice.default ? '— PADRÃO' : ''}`);
});
3.4 Exemplo Completo: Leitor de Texto com Seleção de Voz
const synth = window.speechSynthesis;
let voices = [];
const voiceSelect = document.getElementById('voice-select');
const textInput = document.getElementById('text-input');
const populateVoices = () => {
voices = synth.getVoices();
voiceSelect.innerHTML = '';
voices.forEach((voice, index) => {
const option = document.createElement('option');
option.value = index;
option.textContent = `${voice.name} (${voice.lang})`;
voiceSelect.appendChild(option);
});
};
populateVoices();
if (synth.onvoiceschanged !== undefined) {
synth.onvoiceschanged = populateVoices;
}
document.getElementById('speak-btn').addEventListener('click', () => {
synth.cancel();
const utterance = new SpeechSynthesisUtterance(textInput.value);
utterance.voice = voices[voiceSelect.value];
utterance.pitch = parseFloat(document.getElementById('pitch').value);
utterance.rate = parseFloat(document.getElementById('rate').value);
utterance.onstart = () => {
document.getElementById('status').textContent = 'Falando...';
};
utterance.onend = () => {
document.getElementById('status').textContent = 'Finalizado.';
};
utterance.onerror = (event) => {
document.getElementById('status').textContent = `Erro: ${event.error}`;
};
synth.speak(utterance);
});
document.getElementById('stop-btn').addEventListener('click', () => {
synth.cancel();
document.getElementById('status').textContent = 'Cancelado.';
});
4. Processamento na Nuvem vs. no Dispositivo
SpeechRecognition pode rotear o áudio através de duas pipelines radicalmente diferentes:
4.1 Processamento na Nuvem (Por Padrão)
Por padrão, o Chrome serializa o fluxo de áudio e o envia aos servidores na nuvem do Google para transcrição. Isso oferece alta precisão (modelos neurais profundos, enormes conjuntos de dados de treinamento), mas introduz comprometimentos:
- Latência: viagens de ida e volta pela rede degradam experiências em tempo real
- Privacidade: os dados brutos de áudio saem do dispositivo
- Sem conexão: completamente inoperante sem internet
4.2 Processamento no Dispositivo (Experimental)
Navegadores modernos estão introduzindo inferência local usando modelos no dispositivo. Isso elimina a latência de rede, funciona sem conexão e mantém o áudio privado:
const recognition = new SpeechRecognition();
const availability = await SpeechRecognition.available();
if (availability === 'available') {
recognition.start();
} else if (availability === 'downloadable') {
await SpeechRecognition.install();
recognition.start();
} else {
console.warn('Reconhecimento no dispositivo não suportado — usando a nuvem como fallback.');
recognition.start();
}
Nota:
SpeechRecognition.available()eSpeechRecognition.install()são APIs experimentais. Permitem verificar se os modelos de idioma estão armazenados em cache localmente e ativar seu download. O suporte limita-se a versões recentes do Chromium.
5. Modelo de Segurança
A API Web Speech implementa restrições de segurança rigorosas para prevenir a interceptação passiva de áudio:
- Gesto do usuário necessário:
recognition.start()deve ser invocado dentro de um evento iniciado pelo usuário (clique, toque). Invocá-lo programaticamente sem um gesto lança um erro. - Apenas HTTPS: toda a API é restrita a contextos seguros. Páginas HTTP não podem acessar o microfone.
- Indicadores visuais: navegadores exibem indicadores de gravação persistentes e inalteráveis (ícones nas abas, alertas na barra de status) enquanto o microfone está ativo.
- Permissions-Policy: administradores de servidores podem bloquear o acesso ao microfone no nível de cabeçalho HTTP para iframes de origem cruzada:
Permissions-Policy: microphone=()
6. Compatibilidade entre Navegadores (2026)
| Navegador | SpeechSynthesis (TTS) | SpeechRecognition (STT) | Notas |
|---|---|---|---|
| Chrome | ✅ Completo (v33+) | ✅ Completo (v33+) | Implementador principal. Baseado na nuvem por padrão. |
| Edge | ✅ Completo (v14+) | ⚠️ Não confiável | Expõe a interface, mas os eventos podem nunca disparar. |
| Firefox | ✅ Completo (v49+) | ❌ Não implementado | Objeção filosófica ao roteamento de biometria vocal para nuvens de terceiros. |
| Safari | ✅ Completo (v7+) | ⚠️ Parcial (v14.1+) | Requer prefixo webkit. Depende do Siri estar ativado. |
| Opera | ✅ Completo (v21+) | ❌ Não implementado | Reflete as limitações do Edge/Firefox. |
| Brave | ⚠️ Variável | ❌ Bloqueado | Bloqueia conexões STT à nuvem por privacidade. |
6.1 Detecção Defensiva de Recursos
Sempre verifique o suporte antes de usar qualquer um dos dois subsistemas:
const hasSpeechRecognition = 'SpeechRecognition' in window || 'webkitSpeechRecognition' in window;
const hasSpeechSynthesis = 'speechSynthesis' in window;
if (!hasSpeechRecognition) {
console.warn('SpeechRecognition não disponível. Considere um fallback na nuvem (Google Cloud STT, Azure, AWS Transcribe).');
}
if (!hasSpeechSynthesis) {
console.warn('SpeechSynthesis não disponível.');
}
7. Problemas Conhecidos e Soluções
7.1 Bug dos 15 Segundos no Chrome (TTS)
Vozes baseadas na nuvem no Chrome abortam locuções com mais de ~15 segundos sem disparar eventos end ou error, deixando a interface em um estado de “falando” permanente. A solução é dividir textos longos em locuções menores:
const speakLongText = (text) => {
const synth = window.speechSynthesis;
synth.cancel();
const sentences = text.match(/[^.!?]+[.!?]+/g) || [text];
sentences.forEach((sentence) => {
const utterance = new SpeechSynthesisUtterance(sentence.trim());
utterance.voice = voices[0];
synth.speak(utterance);
});
};
7.2 Safari iOS: TTS Requer um Gesto de “Aquecimento”
O Safari no iOS bloqueia o áudio TTS até que o usuário execute um gesto. Para resolver isso, “aqueça” silenciosamente o contexto de áudio na primeira interação do usuário:
document.addEventListener('click', () => {
const primer = new SpeechSynthesisUtterance('');
window.speechSynthesis.speak(primer);
}, { once: true });
Após isso, chamadas subsequentes para synth.speak() funcionarão sem exigir gestos adicionais.
7.3 SpeechRecognition em PWAs do iOS
Quando uma página web é adicionada à tela de início no iOS (“Adicionar à tela de início”), o webkitSpeechRecognition é completamente desativado. Todas as chamadas lançam erros NotAllowed sem sequer exibir a caixa de permissões. Não há solução dentro da API Web Speech — deve-se usar um serviço STT baseado na nuvem em seu lugar.
8. Benefícios de Acessibilidade
A API Web Speech melhora diretamente a acessibilidade para:
- Usuários com deficiência motora: comandos de voz substituem interações de teclado e mouse
- Usuários com deficiência visual: TTS lê o conteúdo em voz alta, complementando leitores de tela
- Usuários com baixa alfabetização: o feedback auditivo reduz a dependência da leitura
- Cenários mãos livres: cozinha, direção ou ambientes médicos onde a entrada tátil/digitação é impraticável
Melhores práticas ao integrar voz em interfaces acessíveis:
- Desvincular o teclado virtual chamando
element.blur()quando o reconhecimento inicia, recuperando espaço do viewport no celular - Exibir resultados provisórios visualmente para que o usuário veja feedback em tempo real durante o ditado
- Usar o evento
boundarydo TTS para destacar o texto enquanto é lido, oferecendo uma experiência visual-auditiva sincronizada
9. Conclusão
A API Web Speech fornece uma ponte poderosa entre a voz e a plataforma web. O SpeechSynthesis está pronto para produção em todos os navegadores, enquanto o SpeechRecognition requer detecção cuidadosa de recursos e estratégias de fallback devido ao suporte fragmentado.
Comece com TTS para melhorias de acessibilidade — funciona em todos os lugares. Para STT, foque em navegadores Chromium com reconhecimento baseado na nuvem, implemente tratamento robusto de erros e avalie serviços de fallback na nuvem (Google Cloud Speech-to-Text, Azure Cognitive Services, AWS Transcribe) para cobertura entre navegadores.
Fontes: Especificação W3C Web Speech API, MDN Web Docs, rastreador de problemas do Chromium, dados de compatibilidade (caniuse.com). Os exemplos correspondem ao estado da API em março de 2026.