{"version":3,"file":"azure.cjs","sources":["../../src/providers/AzureProvider.ts"],"sourcesContent":["import type { TTSProvider, SpeakOptions, VoiceInfo, SpeechError, AzureConfig } from './types.js'\nimport { playBlobAudio, throwIfNotOk } from './audioUtils.js'\n\n// I-3.3: SSML template + output format header\nconst OUTPUT_FORMAT = 'audio-16khz-128kbitrate-mono-mp3'\n\nfunction buildSSML(voice: string, text: string): string {\n  // Escape XML special characters to prevent injection\n  const escaped = text\n    .replace(/&/g, '&amp;')\n    .replace(/</g, '&lt;')\n    .replace(/>/g, '&gt;')\n    .replace(/\"/g, '&quot;')\n    .replace(/'/g, '&apos;')\n  return `<speak version='1.0' xml:lang='en-US'><voice name='${voice}'>${escaped}</voice></speak>`\n}\n\nexport class AzureProvider implements TTSProvider {\n  readonly isSupported: boolean = typeof window !== 'undefined'\n\n  private _config: AzureConfig\n  private _abortController: AbortController | null = null\n\n  private _startCbs: Array<() => void> = []\n  private _endCbs: Array<() => void> = []\n  private _errorCbs: Array<(err: SpeechError) => void> = []\n\n  constructor(config: AzureConfig) {\n    if (config.baseURL && !/^https:\\/\\//i.test(config.baseURL)) {\n      throw new Error('[vue-text-to-speech] Azure baseURL must use HTTPS')\n    }\n    this._config = config\n  }\n\n  onStart(cb: () => void): void { this._startCbs.push(cb) }\n  onEnd(cb: () => void): void   { this._endCbs.push(cb) }\n  onError(cb: (err: SpeechError) => void): void { this._errorCbs.push(cb) }\n\n  async speak(options: SpeakOptions): Promise<void> {\n    if (!options.text) {\n      const err: SpeechError = { code: 'API_ERROR', message: 'Text must not be empty' }\n      this._errorCbs.forEach((cb) => cb(err))\n      throw err\n    }\n    if (options.text.length > 10_000) {\n      const err: SpeechError = { code: 'API_ERROR', message: 'Text exceeds maximum length of 10,000 characters' }\n      this._errorCbs.forEach((cb) => cb(err))\n      throw err\n    }\n\n    this.stop()\n\n    const controller = new AbortController()\n    this._abortController = controller\n\n    const voice = options.voice?.id ?? this._config.voice ?? 'en-US-JennyNeural'\n    const endpoint = this._config.baseURL\n      ? `${this._config.baseURL.replace(/\\/$/, '')}/cognitiveservices/v1`\n      : `https://${this._config.region}.tts.speech.microsoft.com/cognitiveservices/v1`\n\n    let response: Response\n    try {\n      response = await fetch(endpoint, {\n        method: 'POST',\n        signal: controller.signal,\n        headers: {\n          'Content-Type': 'application/ssml+xml',\n          'X-Microsoft-OutputFormat': OUTPUT_FORMAT,\n          'Ocp-Apim-Subscription-Key': this._config.subscriptionKey,\n        },\n        body: buildSSML(voice, options.text),\n      })\n    } catch (e: unknown) {\n      if ((e as Error)?.name === 'AbortError') return\n      const err: SpeechError = { code: 'NETWORK', message: 'Azure TTS fetch failed', cause: e }\n      this._errorCbs.forEach((cb) => cb(err))\n      throw err\n    }\n\n    await throwIfNotOk(response)\n\n    const blob = await response.blob()\n\n    this._startCbs.forEach((cb) => cb())\n    try {\n      await playBlobAudio(blob, controller.signal)\n    } catch (e: unknown) {\n      const err = e as SpeechError\n      this._errorCbs.forEach((cb) => cb(err))\n      throw err\n    } finally {\n      this._abortController = null\n      this._endCbs.forEach((cb) => cb())\n    }\n  }\n\n  stop(): void {\n    this._abortController?.abort()\n    this._abortController = null\n  }\n\n  pause(): void  { /* no-op */ }\n  resume(): void { /* no-op */ }\n\n  async getVoices(): Promise<VoiceInfo[]> {\n    const endpoint = this._config.baseURL\n      ? `${this._config.baseURL.replace(/\\/$/, '')}/cognitiveservices/voices/list`\n      : `https://${this._config.region}.tts.speech.microsoft.com/cognitiveservices/voices/list`\n\n    const response = await fetch(endpoint, {\n      headers: { 'Ocp-Apim-Subscription-Key': this._config.subscriptionKey },\n    })\n    await throwIfNotOk(response)\n\n    const data = (await response.json()) as Array<{\n      ShortName: string\n      DisplayName: string\n      Locale: string\n      Gender: string\n    }>\n\n    return data.map((v, i) => ({\n      id: v.ShortName,\n      name: v.DisplayName,\n      lang: v.Locale,\n      label: `${v.DisplayName} (${v.Locale})`,\n      default: i === 0,\n    }))\n  }\n}\n"],"names":["throwIfNotOk","playBlobAudio"],"mappings":"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"}