const KEY = import.meta.env.VITE_AZURE_SPEECH_KEY as string | undefined const REGION = import.meta.env.VITE_AZURE_SPEECH_REGION as string | undefined // 美式男声,口齿清晰、随和。备选(都是 en-US-*Neural): // GuyNeural / AndrewNeural / ChristopherNeural / TonyNeural(男) // JennyNeural / EmmaNeural / AvaNeural(女,清晰度也高) const VOICE = 'en-US-JennyNeural' const FORMAT = 'audio-24khz-48kbitrate-mono-mp3' // 默认偏自然偏快;调慢改 -10%、调更快改 +25% 之类。 const RATE = '+10%' /** * Synthesize English text via Azure Speech REST. * Returns an MP3 Blob. Throws on credential / network / non-2xx. * * Pass an AbortSignal so callers (the audio player) can cancel * an in-flight synthesis when the user starts recording or * triggers a different playback. */ export async function synthesize(text: string, signal?: AbortSignal): Promise { if (!KEY || !REGION) { throw new Error('Azure Speech credentials not configured (VITE_AZURE_SPEECH_KEY / VITE_AZURE_SPEECH_REGION)') } const ssml = `` + `` + `${escapeXml(text)}` + `` + `` const res = await fetch( `https://${REGION}.tts.speech.microsoft.com/cognitiveservices/v1`, { method: 'POST', signal, headers: { 'Ocp-Apim-Subscription-Key': KEY, 'Content-Type': 'application/ssml+xml', 'X-Microsoft-OutputFormat': FORMAT, 'User-Agent': 'PPT-EnglishSpeaking', }, body: ssml, }, ) if (!res.ok) { throw new Error(`Azure TTS failed: ${res.status} ${res.statusText}`) } return res.blob() } function escapeXml(s: string): string { return s.replace(/[<>&'"]/g, c => ({ '<': '<', '>': '>', '&': '&', "'": ''', '"': '"', }[c]!)) }