Aprenda

Conversão de texto em fala em JavaScript com SpeechSynthesis

Rene, Kabir Goel 
Conversão de texto em fala em JavaScript com SpeechSynthesis

Para converter texto em fala em JavaScript, crie um SpeechSynthesisUtterance e passe-o para window.speechSynthesis.speak(). O navegador cuida da reprodução. Você não precisa de chave de API, servidor ou pacote JavaScript.

Isso funciona bem para um protótipo ou um controle de leitura em voz alta quando as vozes do dispositivo são suficientes. Se você está criando um agente de voz que precisa manter a mesma voz entre dispositivos ou transmitir áudio para uma chamada telefônica, use uma API de texto em fala hospedada. O SpeechSynthesis do navegador e a API da Cartesia são formas distintas de gerar fala.

Como funciona a síntese de fala no navegador

window.speechSynthesis é o controlador de fala sintetizada da Web Speech API. Um enunciado contém o texto e as configurações de uma solicitação. Chamar speak() adiciona o enunciado a uma fila; não retorna um arquivo de áudio.1

O navegador expõe as vozes disponíveis no dispositivo atual por meio de getVoices(). Essa lista pode carregar depois que seu script for executado, então leia-a imediatamente e atualize-a quando o evento voiceschanged ocorrer.2 Não fixe no código o nome de uma voz do seu notebook esperando que ela exista no celular de um cliente.

1. Salve uma demonstração funcional de texto em fala

Salve o código abaixo como speech.html e abra-o em um navegador. Digite uma frase curta, escolha uma voz se o navegador listar alguma e pressione Speak. Use texto de exemplo sem informações sensíveis: algumas vozes usam serviços de fala remotos.3

<!doctype html>
<html lang="en">
	<head>
		<meta charset="utf-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1" />
		<title>JavaScript text-to-speech demo</title>
	</head>
	<body>
		<h1>Read text aloud</h1>
		<p>
			<label for="text">Text to speak</label><br />
			<textarea id="text" rows="4" cols="30" maxlength="500">
Hello from your browser.</textarea
			>
		</p>
		<p>
			<label for="voice">Voice</label>
			<select id="voice"></select>
		</p>
		<button id="speak" type="button">Speak</button>
		<button id="stop" type="button">Stop</button>
		<p id="status" role="status" aria-live="polite">Ready.</p>

		<script>
			const text = document.querySelector("#text");
			const voiceSelect = document.querySelector("#voice");
			const speakButton = document.querySelector("#speak");
			const stopButton = document.querySelector("#stop");
			const status = document.querySelector("#status");
			let voices = [];
			let currentUtterance = null;

			if (
				!("speechSynthesis" in window) ||
				!("SpeechSynthesisUtterance" in window)
			) {
				status.textContent = "This browser does not support speech synthesis.";
				speakButton.disabled = true;
				stopButton.disabled = true;
			} else {
				const synth = window.speechSynthesis;

				function loadVoices() {
					const previousVoice = voiceSelect.value;
					voices = synth.getVoices();
					voiceSelect.replaceChildren(new Option("Browser default", ""));
					for (const voice of voices) {
						voiceSelect.add(
							new Option(`${voice.name} (${voice.lang})`, voice.voiceURI),
						);
					}
					if (voices.some((voice) => voice.voiceURI === previousVoice)) {
						voiceSelect.value = previousVoice;
					}
				}

				loadVoices();
				synth.addEventListener("voiceschanged", loadVoices);

				speakButton.addEventListener("click", () => {
					const transcript = text.value.trim();
					if (!transcript) {
						status.textContent = "Enter some text first.";
						return;
					}

					// Ignore callbacks from the utterance being replaced.
					currentUtterance = null;
					synth.cancel();
					const utterance = new SpeechSynthesisUtterance(transcript);
					const voice = voices.find(
						(item) => item.voiceURI === voiceSelect.value,
					);
					if (voice) {
						utterance.voice = voice;
						utterance.lang = voice.lang;
					} else {
						utterance.lang = document.documentElement.lang;
					}
					utterance.rate = 1;
					currentUtterance = utterance;
					status.textContent = "Starting speech...";
					utterance.onstart = () => {
						if (currentUtterance === utterance)
							status.textContent = "Speaking...";
					};
					utterance.onend = () => {
						if (currentUtterance !== utterance) return;
						currentUtterance = null;
						status.textContent = "Finished.";
					};
					utterance.onerror = (event) => {
						if (currentUtterance !== utterance) return;
						currentUtterance = null;
						status.textContent = `Speech failed: ${event.error}`;
					};
					synth.speak(utterance);
				});

				stopButton.addEventListener("click", () => {
					currentUtterance = null;
					synth.cancel();
					status.textContent = "Stopped.";
				});
			}
		</script>
	</body>
</html>

A demonstração limita a entrada a 500 caracteres para manter o primeiro teste curto. Esse é um limite da demonstração, não da Web Speech API. Teste trechos mais longos separadamente em cada navegador compatível com sua aplicação.

2. Verifique a seleção de voz e a reprodução

O seletor de vozes mostra o nome e o idioma de cada voz disponível. Escolha uma voz que corresponda ao idioma do texto; definir utterance.lang não traduz as palavras.

Speak é executado ao clicar no botão, não ao carregar a página. Isso dá controle ao ouvinte e evita depender da reprodução automática. Pressionar Speak novamente cancela a solicitação atual antes de iniciar outra, em vez de enfileirar cliques repetidos. Stop chama cancel(), que remove os enunciados da fila e interrompe a fala atual.4

A verificação de currentUtterance impede que um callback tardio de uma solicitação cancelada sobrescreva o estado de uma solicitação mais recente. Se adaptar esse código para um componente, remova o listener de voiceschanged quando o componente for desmontado. Cancele a reprodução na desmontagem apenas se esse componente for responsável pela fila de fala da página.

3. Resolva problemas de silêncio ou fala inconsistente

SintomaO que verificar
O seletor mostra apenas Browser defaultAguarde voiceschanged. Se a lista continuar vazia, verifique o suporte do navegador e as vozes instaladas no sistema. A demonstração ainda pode solicitar uma voz padrão.
Nenhum som após pressionar SpeakVerifique o volume e a saída de áudio do dispositivo, tente uma frase curta no idioma correspondente e leia a mensagem de estado para identificar um erro de síntese.
Uma voz existe no computador, mas não no celularAs listas de vozes dependem do dispositivo. Mantenha uma opção padrão e teste nos sistemas operacionais reais que você oferece suporte.
Cliques repetidos atrasam a falaspeak() enfileira solicitações. Cancele primeiro a solicitação antiga se o novo texto precisar substituí-la.
A fala falha sem conexão com a internetA voz selecionada pode usar um serviço remoto. Inspecione voice.localService e teste offline em vez de presumir síntese local.

Para uma função de leitura em voz alta, mantenha o texto original visível e torne Stop acessível pelo teclado. Não inicie a fala automaticamente nem trate a fala sintetizada como substituta do suporte a leitores de tela.

Quando usar uma API de TTS hospedada

Escolha a fala do navegador quando as vozes disponíveis para o ouvinte forem suficientes e você só precisar reproduzir áudio naquele dispositivo. Escolha uma API hospedada quando sua aplicação precisar dos bytes do áudio gerado, de uma identidade de voz gerenciada pelo provedor ou de áudio entregue fora do navegador.

RequisitoSpeechSynthesis do navegadorAPI de TTS hospedada, como a Cartesia
CredenciaisSem chave de API da aplicaçãoCredenciais de API e conta com acesso de uso
Seleção de vozLista de vozes do navegador e do sistema operacionalBiblioteca de vozes do provedor ou voz personalizada autorizada
Destino do áudioReprodução no navegadorSua aplicação recebe áudio para reprodução, armazenamento ou transporte
Funcionamento offlineDepende da voz e do dispositivoSolicitações à API da Cartesia exigem conexão de rede
Tratamento do textoLocal ou remoto, conforme a vozO texto é enviado ao provedor; revise os requisitos de dados antes da integração

Para usar a síntese de fala da Cartesia, chame a API pelo seu backend. Mantenha a chave de API lá, não em um arquivo HTML público ou no bundle do cliente. Uma aplicação no navegador pode enviar texto ao seu backend autenticado, que aplica limites de entrada e retorna áudio ao cliente. Limite a taxa de solicitações desse endpoint para que visitantes não possam consumir seus créditos de API sem restrição.

Comece gerando um arquivo WAV com o SDK Python da Cartesia. Esse guia testa o caminho de geração hospedada; não é um reprodutor de streaming no navegador. Para uma aplicação conversacional, você também precisará de buffer de reprodução e tratamento de interrupções. Compare esse trabalho com o Cartesia Managed Agents antes de construir sozinho todo o pipeline do agente de voz.

Referências relacionadas

Notas de rodapé

  1. MDN, SpeechSynthesis. ↩

  2. MDN, SpeechSynthesis.getVoices(). ↩

  3. MDN, SpeechSynthesisVoice.localService. ↩

  4. MDN, SpeechSynthesis.cancel(). ↩

Perguntas frequentes

JavaScript pode converter texto em fala sem uma chave de API?

Sim. Nos navegadores compatíveis, window.speechSynthesis lê o texto com as vozes disponíveis no dispositivo. Você não precisa de uma conta Cartesia nem de uma chave de API para este exemplo no navegador. A disponibilidade e o comportamento das vozes variam conforme o navegador e o sistema operacional.

Por que speechSynthesis.getVoices() retorna um array vazio?

O navegador talvez ainda não tenha carregado as vozes. Chame getVoices na inicialização e escute voiceschanged para atualizar a lista. Se ela continuar vazia, verifique o suporte a fala do navegador e as vozes instaladas no sistema operacional.

SpeechSynthesis pode salvar um arquivo MP3 ou WAV?

SpeechSynthesis reproduz fala pelo navegador; não oferece uma API que retorne o áudio gerado como arquivo ou fluxo de bytes. Use um serviço de fala com um endpoint de saída de áudio se sua aplicação precisar salvar ou transmitir o resultado.

A síntese de fala do navegador funciona offline?

Depende da voz. SpeechSynthesisVoice.localService indica se uma voz vem de um serviço de fala local ou remoto. Não presuma que toda voz do navegador funciona offline ou mantém o texto no dispositivo. Verifique a voz escolhida nos dispositivos de destino.