Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
npm install @aws-sdk/client-polly// lib/polly.ts
import { PollyClient } from "@aws-sdk/client-polly";
export const pollyClient = new PollyClient({
region: process.env.AWS_REGION!,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
},
});// app/api/speech/route.ts
import { SynthesizeSpeechCommand } from "@aws-sdk/client-polly";
import { pollyClient } from "@/lib/polly";
export async function POST(req: Request) {
const { text, voiceId = "Joanna" } = await req.json();
const command = new SynthesizeSpeechCommand({
Text: text,
OutputFormat: "mp3",
VoiceId: voiceId,
Engine: "neural",
});
const response = await pollyClient.send(command);
const stream = response.AudioStream as ReadableStream;
return new Response(stream, {
headers: {
"Content-Type": "audio/mpeg",
"Cache-Control": "public, max-age=3600",
},
});
}Quando usar isso: Você precisa converter texto em fala com som natural em um aplicativo web para acessibilidade, assistentes de voz, aprendizado de idiomas ou narração de conteúdo.
// app/components/TextToSpeech.tsx
"use client";
import { useState, useRef } from "react";
const VOICES = [
{ id: "Joanna", name: "Joanna (US English)", lang: "en-US" },
{ id: "Matthew", name: "Matthew (US English)", lang: "en-US" },
{ id: "Amy", name: "Amy (British English)", lang: "en-GB" },
{ id: "Lea", name: "Léa (French)", lang: "fr-FR" },
{ id: "Vicki", name: "Vicki (German)", lang: "de-DE" },
{ id: "Lucia", name: "Lucia (Spanish)", lang: "es-ES" },
];
export default function TextToSpeech() {
const [text, setText] = useState("");
const [voiceId, setVoiceId] = useState("Joanna");
const [loading, setLoading] = useState(false);
const audioRef = useRef<HTMLAudioElement>(null);
async function handleSpeak() {
if (!text.trim()) return;
setLoading(true);
try {
const res = await fetch("/api/speech", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text, voiceId }),
});
if (!res.ok) throw new Error("Speech synthesis failed");
const blob = await res.blob();
const url = URL.createObjectURL(blob);
if (audioRef.current) {
audioRef.current.src = url;
audioRef.current.play();
}
} catch (error) {
console.error("TTS error:", error);
} finally {
setLoading(false);
}
}
return (
<div className="max-w-md mx-auto p-6 space-y-4">
<textarea
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Enter text to speak..."
rows={4}
className="w-full border rounded px-3 py-2"
maxLength={3000}
/>
<div className="flex gap-3">
<select
value={voiceId}
onChange={(e) => setVoiceId(e.target.value)}
className="border rounded px-3 py-2"
>
{VOICES.map((v) => (
<option key={v.id} value={v.id}>
{v.name}
</option>
))}
</select>
<button
onClick={handleSpeak}
disabled={loading || !text.trim()}
className="bg-blue-600 text-white px-4 py-2 rounded disabled:opacity-50"
>
{loading ? "Generating..." : "Speak"}
</button>
</div>
<audio ref={audioRef} controls className="w-full" />
<p className="text-xs text-gray-500">{text.length}/3000 characters</p>
</div>
);
}O que isso demonstra:
SynthesizeSpeechCommand retorna um AudioStream que pode ser canalizado diretamente para a respostamp3, ogg_vorbis, pcm e json (para speech marks)SSML para controle avançado de fala:
const command = new SynthesizeSpeechCommand({
Text: `<speak>
Bem-vindo ao nosso aplicativo.
<break time="500ms"/>
<prosody rate="slow" pitch="+10%">
Esta parte é falada lentamente com um tom mais alto.
</prosody>
<emphasis level="strong">Isso é importante.</emphasis>
<say-as interpret-as="date" format="mdy">12/25/2025</say-as>
</speak>`,
TextType: "ssml",
OutputFormat: "mp3",
VoiceId: "Joanna",
Engine: "neural",
});Obter vozes disponíveis:
import { DescribeVoicesCommand } from "@aws-sdk/client-polly";
export async function GET() {
const command = new DescribeVoicesCommand({
Engine: "neural",
LanguageCode: "en-US",
});
const response = await pollyClient.send(command);
const voices = response.Voices?.map((v) => ({
id: v.Id,
name: v.Name,
gender: v.Gender,
languageName: v.LanguageName,
}));
return Response.json(voices);
}Speech marks (dados de tempo de palavras):
const command = new SynthesizeSpeechCommand({
Text: "Hello, how are you today?",
OutputFormat: "json",
VoiceId: "Joanna",
Engine: "neural",
SpeechMarkTypes: ["word", "sentence"],
});
const response = await pollyClient.send(command);
// Retorna JSONL com dados de tempo para cada palavra/sentença
// {"time":0,"type":"sentence","start":0,"end":25,"value":"Hello, how are you today?"}
// {"time":0,"type":"word","start":0,"end":5,"value":"Hello"}Server Action para frases curtas:
"use server";
import { SynthesizeSpeechCommand } from "@aws-sdk/client-polly";
import { pollyClient } from "@/lib/polly";
export async function synthesizeSpeech(text: string, voiceId: string = "Joanna") {
const command = new SynthesizeSpeechCommand({
Text: text.slice(0, 3000),
OutputFormat: "mp3",
VoiceId: voiceId,
Engine: "neural",
});
const response = await pollyClient.send(command);
const chunks: Uint8Array[] = [];
const stream = response.AudioStream as AsyncIterable<Uint8Array>;
for await (const chunk of stream) {
chunks.push(chunk);
}
const buffer = Buffer.concat(chunks);
return buffer.toString("base64");
}VoiceId é um tipo de união de todos os IDs de voz disponíveis (por exemplo, "Joanna", "Matthew")Engine é "neural" ou "standard"OutputFormat é "mp3" ou "ogg_vorbis" ou "pcm" ou "json"AudioStream o tipo varia por ambiente; converta para ReadableStream em serverlessimport type {
SynthesizeSpeechCommandInput,
VoiceId,
Engine,
} from "@aws-sdk/client-polly";
const voiceId: VoiceId = "Joanna";
const engine: Engine = "neural";
const input: SynthesizeSpeechCommandInput = {
Text: "Hello",
OutputFormat: "mp3",
VoiceId: voiceId,
Engine: engine,
};Mecanismo neural não disponível para todas as vozes - Nem toda voz suporta o mecanismo neural. Correção: Use DescribeVoicesCommand com Engine: "neural" para obter a lista de vozes suportadas. Use "standard" como fallback se não tiver certeza.
Limites de comprimento de texto - O mecanismo padrão permite 6000 caracteres, o neural permite 3000 por solicitação. Correção: Divida textos longos em partes nas fronteiras das sentenças e sintetize cada uma separadamente.
SSML deve ser XML bem formado - Tags SSML inválidas causam erro em SynthesizeSpeechCommand. Correção: Defina TextType: "ssml" e envolva o conteúdo em tags <speak>. Escape caracteres especiais (&, <).
Compatibilidade do formato de áudio - A saída pcm é dados de áudio brutos, não reproduzível em um elemento de áudio do navegador. Correção: Use mp3 ou ogg_vorbis para reprodução no navegador. Use pcm apenas para pipelines de processamento de áudio.
Custo em escala - O Polly cobra por caractere sintetizado. Correção: Armazene em cache os resultados de áudio no S3 ou em uma CDN para frases repetidas. Use o cabeçalho Cache-Control nas respostas.
Tipo de corpo de streaming varia - AudioStream é tipado de forma diferente em Node.js vs. runtimes de borda. Correção: Em rotas de API Node.js, converta para ReadableStream. Em runtime de borda, ele pode já ser um stream da web.
| Biblioteca | Melhor Para | Contraponto |
|---|---|---|
| AWS Polly | Vozes neurais de alta qualidade, controle SSML | Requer conta AWS, custo por caractere |
| Web Speech API | TTS nativo do navegador gratuito | Qualidade inconsistente, controle de voz limitado |
| Google Cloud TTS | Vozes WaveNet, amplo suporte a idiomas | SDK diferente, precificação semelhante |
| ElevenLabs | Clonagem de voz ultrarrealista | Custo mais alto, API separada |
| OpenAI TTS | API simples, boa qualidade | Personalização de voz limitada |
DescribeVoicesCommandconst response = await pollyClient.send(command);
const stream = response.AudioStream as ReadableStream;
return new Response(stream, {
headers: { "Content-Type": "audio/mpeg" },
});O navegador recebe o stream de áudio e pode reproduzi-lo através de um elemento <audio>.
mp3 -- amplamente suportado, bom para reprodução no navegadorogg_vorbis -- boa qualidade, suportado na maioria dos navegadores modernospcm -- dados de áudio brutos, não reproduzíveis diretamente em um navegadorjson -- retorna speech marks (dados de tempo), não áudiomp3 para reprodução no navegador na maioria dos casosconst command = new SynthesizeSpeechCommand({
Text: `<speak>
Olá. <break time="500ms"/>
<prosody rate="slow">Isso é lento.</prosody>
<emphasis level="strong">Importante!</emphasis>
</speak>`,
TextType: "ssml",
OutputFormat: "mp3",
VoiceId: "Joanna",
Engine: "neural",
});<speak>TextType: "ssml" no comando -- omitir isso trata as tags SSML como texto puro& para &, < para <SynthesizeSpeechCommandimport { DescribeVoicesCommand } from "@aws-sdk/client-polly";
const command = new DescribeVoicesCommand({
Engine: "neural",
LanguageCode: "en-US",
});
const response = await pollyClient.send(command);
const voices = response.Voices?.map(v => ({
id: v.Id,
name: v.Name,
gender: v.Gender,
}));OutputFormat: "json" e SpeechMarkTypes: ["word", "sentence"]time, start, end e valueAudioStream é um stream Readable do Node.jsReadableStream da webReadableStream ao retornar de um construtor Responsefor await e colete em um Bufferimport type {
SynthesizeSpeechCommandInput,
VoiceId,
Engine,
} from "@aws-sdk/client-polly";
const input: SynthesizeSpeechCommandInput = {
Text: "Hello",
OutputFormat: "mp3",
VoiceId: "Joanna" satisfies VoiceId,
Engine: "neural" satisfies Engine,
};Cache-Control nas respostas da API para cache do navegador"use server";
const response = await pollyClient.send(command);
const chunks: Uint8Array[] = [];
for await (const chunk of response.AudioStream as AsyncIterable<Uint8Array>) {
chunks.push(chunk);
}
return Buffer.concat(chunks).toString("base64");No cliente, converta base64 para um URL de blob para o elemento <audio>.
Revisado por Chris St. John·Última atualização: 16 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥