import { SpeechVoiceLike } from '../voice/speechVoices'; export type { SpeechVoiceLike }; export interface SpeechSpeakOptions { /** Chosen by `selectVoiceForLocale`; `null` lets the engine resolve `lang`. */ voice: SpeechVoiceLike | null; /** BCP-47 tag for the app locale, used when `voice` is null. */ lang: string; /** 0.1–10, browser default 1. */ rate?: number; /** * `false`: speak with THIS voice or report an error — never a substitute. * * A reply wants to be heard, so a routing engine may re-speak it on another * engine with another voice when the chosen one fails. An audition does not: * the user asked to hear one specific voice, and playing a different one under * its name lets them pick or reject a voice on the strength of one they never * heard (BOFF-7283). Omitted means fallback is allowed. */ fallback?: boolean; } /** * Why playback stopped. `cancelled` is not a failure: it is what every queued * utterance reports when we deliberately cancel (new playback, panel close, * conversation switch), and the UI must stay silent about it. */ export type SpeechErrorReason = 'cancelled' | 'not-allowed' | 'unsupported' | 'unknown'; export interface SpeechSpeakHandlers { /** * Audio has actually begun playing — not merely been requested. * * Exists so a caller can tell "this engine failed before the user heard * anything" from "it failed part-way through". `routingSpeechEngine` uses it * to decide whether retrying on the other engine is safe: retrying after * audio has been heard would repeat words the user already got. * * Optional, so an engine that cannot report it simply never calls it, and a * caller that does not care ignores it. */ onStart?: () => void; /** * Chunk `index` has begun playing — the sync signal for live captions. * * ★ Per CHUNK, not per word. `onboundary` would give word granularity but * exists only on the browser engine; the hosted engine plays one audio element * per chunk and has no equivalent. A chunk start is the finest granularity * BOTH engines can honestly report, so the caption never has to know which one * is speaking. * * Without it the whole reply is captioned the instant playback begins — which * is what "captions appear multiple sentences ahead of the audio" means in * practice: the user reads the answer, then waits for the voice to catch up. * * Optional. An engine that cannot report it never calls it and the caption * falls back to showing the reply whole: degrades, never breaks. */ onChunkStart?: (index: number) => void; /** The whole queue finished on its own. */ onEnd?: () => void; onError?: (reason: SpeechErrorReason) => void; } export interface SpeechEngine { /** Stable id, for logging and for telling engines apart in a future swap. */ readonly id: string; /** False when the platform has no usable speech output at all. */ isSupported(): boolean; /** May be empty before the engine has loaded its voice list — see `voiceAvailability`. */ listVoices(): SpeechVoiceLike[]; /** Subscribe to voice-list changes; returns an unsubscribe function. */ subscribeVoices(listener: () => void): () => void; /** Speak the chunks in order. Implementations MUST cancel anything in flight first. */ speak(chunks: readonly string[], options: SpeechSpeakOptions, handlers?: SpeechSpeakHandlers): void; /** Stop immediately and drop anything queued. Safe to call when idle. */ cancel(): void; } //# sourceMappingURL=types.d.ts.map