/**
 * AudioEngine — browser audio playback & caching with 3-tier storage.
 *
 * Cache tiers: Memory (Blob URL + AudioBuffer) → IndexedDB → Network TTS
 *
 * 工具函数（常量、IndexedDB、MIDI、超时估算、缓存键）已拆分至 AudioEngine.utils.js。
 */
/** 播放回调集合（play / _playAudioBuffer / _playFromUrl / _playMidiSequence 共用） */
interface AEPlayCallbacks {
    onEnd?: (() => void) | null;
    onStart?: (() => void) | null;
    onError?: ((err?: unknown) => void) | null;
    generation: number;
    text?: string;
    refreshSafetyTimeoutFromMetadata?: boolean;
}
export default class AudioEngine {
    options: Record<string, unknown>;
    _audioContext: AudioContext | null;
    _resumePromise: Promise<void> | null;
    _outputGainNode: GainNode | null;
    _outputGainContext: AudioContext | null;
    _outputVolume: number;
    _blobUrlCache: Map<string, string>;
    _urlCache: Map<string, {
        url: string | null;
        status: string;
        [key: string]: unknown;
    }>;
    _pendingLoads: Map<string, Promise<string | null>>;
    _audioBufferCache: Map<string, AudioBuffer>;
    _midiSequenceCache: Map<string, Record<string, unknown>>;
    _sourceNode: AudioBufferSourceNode | null;
    _audioElement: HTMLAudioElement | null;
    _pooledAudio: HTMLAudioElement | null;
    _urlPlaybackToken: number;
    _playGeneration: number;
    _safetyTimeoutId: ReturnType<typeof setTimeout> | null;
    _midiNodes: Set<unknown>;
    _midiEndTimerId: ReturnType<typeof setTimeout> | null;
    _persistentQueue: Array<Record<string, unknown>>;
    _persistentRunning: boolean;
    _persistentCancelled: boolean;
    _persistentTimerId: ReturnType<typeof setTimeout> | null;
    [key: string]: unknown;
    constructor(options?: Record<string, unknown>);
    static getShared(options?: Record<string, unknown>): AudioEngine;
    configure(options?: Record<string, unknown>): this;
    setVolume(volume: unknown): this;
    getVolume(): number;
    isSupported(): boolean;
    _getAudioContextCtor(): typeof AudioContext | null;
    getContext(options?: Record<string, unknown>): AudioContext;
    getOutputNode(options?: Record<string, unknown>): GainNode;
    getDestination(options?: Record<string, unknown>): GainNode;
    resume(options?: Record<string, unknown>): Promise<AudioContext>;
    /**
     * Check whether the AudioContext is in a usable (running) state.
     * Returns false if context hasn't been created or is suspended/closed.
     */
    isContextRunning(): boolean;
    bindUserGesture(target: (EventTarget & {
        addEventListener?: unknown;
        removeEventListener?: unknown;
    }) | null, options?: Record<string, unknown>): () => void;
    /**
     * Build a deterministic cache key from text + speech params.
     */
    buildCacheKey(text: string, options?: Record<string, unknown>): string;
    /**
     * Get a cached Blob URL or CDN URL (synchronous, memory only).
     * @param {string} text
     * @param {object} [options] speech params
     * @returns {string|null}
     */
    getCachedUrl(text: string, options?: Record<string, unknown>): string | null;
    /**
     * Get a pre-decoded AudioBuffer (synchronous, memory only).
     */
    getAudioBuffer(text: string, options?: Record<string, unknown>): AudioBuffer | null;
    /**
     * Get a normalized cached MIDI sequence (synchronous, memory only).
     */
    getMidiSequence(text: string, options?: Record<string, unknown>): Record<string, unknown> | null;
    _cacheAudioBlob(cacheKey: string, blob: unknown, options?: Record<string, unknown>): string | null;
    _cacheAudioUrl(cacheKey: string, url: string, options?: Record<string, unknown>): string | null;
    _cacheAudioSource(cacheKey: string, source: unknown, options?: Record<string, unknown>): string | null;
    _cacheMidiSequence(cacheKey: string, sequence: unknown, options?: Record<string, unknown>): Record<string, unknown> | null;
    /**
     * Store an externally-obtained audio URL in the cache and
     * asynchronously download + persist its Blob to IndexedDB.
     */
    set(text: string, source: unknown, options?: Record<string, unknown>): unknown;
    setUrl(text: string, url: string, options?: Record<string, unknown>): unknown;
    setAudioFile(text: string, file: unknown, options?: Record<string, unknown>): unknown;
    setAudioData(text: string, data: unknown, options?: Record<string, unknown>): unknown;
    setMidiSequence(text: string, sequence: unknown, options?: Record<string, unknown>): unknown;
    /**
     * Preload a single text → audio entry through three tiers:
     *   memory Blob URL → IndexedDB → network TTS API
     *
     * @param {string} text
     * @param {object} [options] speech params
     * @param {function} [options.fetchAudioUrl] async (text, params) => url — custom TTS fetcher
     * @returns {Promise<string|null>} playable URL
     */
    preload(text: string, options?: Record<string, unknown>): Promise<string | null>;
    /**
     * Batch-preload multiple items with concurrency control.
     * @param {Array<string|{text:string, options?:object}>} items
     * @param {number} [concurrency=3]
     */
    preloadBatch(items: Array<string | {
        text: string;
        options?: Record<string, unknown>;
    }>, concurrency?: number): Promise<void>;
    /**
     * Background persistent preload queue with automatic retry.
     */
    preloadPersistent(items: Array<string | {
        text: string;
        options?: Record<string, unknown>;
    }>, concurrency?: number, retryDelay?: number, batchDelay?: number): Promise<void>;
    cancelPreload(): void;
    /**
     * Warm up memory caches from IndexedDB at app startup.
     * @returns {Promise<Array<{text:string, options?:object}>>} items not found in IndexedDB
     */
    warmupFromDB(items: Array<string | {
        text: string;
        options?: Record<string, unknown>;
    }>): Promise<Array<{
        text: string;
        options?: Record<string, unknown>;
    }>>;
    /**
     * Decode a Blob into an AudioBuffer and cache it.
     * On mobile the AudioContext may be suspended; decodeAudioData still works
     * in most browsers but we guard against failures gracefully.
     * @private
     */
    _decodeToBuffer(cacheKey: string, blob: Blob): Promise<void>;
    /**
     * Play audio for the given text, using the best available cached source.
     *
     * Resolution order: pre-decoded AudioBuffer → Blob/CDN URL → skip.
     *
     * On mobile, if the AudioContext is suspended (no user gesture), the method
     * attempts `resume()` but does NOT block indefinitely. Instead it fires
     * `onEnd` after a reasonable timeout derived from the AudioBuffer duration
     * or estimated from text length.
     *
     * @param {string} text
     * @param {object} [options]
     * @param {function} [options.onEnd]
     * @param {function} [options.onStart]
     * @param {function} [options.onError]
     * @param {number}   [options.timeout] explicit safety timeout override (ms)
     * @returns {{ type: string }|null} playback descriptor or null if nothing to play
     */
    play(text: string, options?: Record<string, unknown>): {
        type: string;
    } | null;
    /**
     * Play an AudioBuffer via Web Audio API.
     * Attempts resume(); if the context stays suspended after a short wait,
     * fires onEnd via the safety timeout rather than hanging forever.
     * @private
     */
    _playAudioBuffer(audioBuffer: AudioBuffer, { onEnd, onStart, onError, generation }: AEPlayCallbacks): Promise<void>;
    /**
     * Get the shared pooled HTMLAudioElement used by `_playFromUrl`.
     *
     * WHY POOLED: On mobile WebViews (especially WeChat X5 / iOS Safari),
     * each freshly-created `new Audio()` element requires its own user-gesture
     * unlock. If we kept creating new elements, only the very first one
     * (played during a user gesture) would be audible — subsequent cache-hit
     * plays would silently fail while still resolving their promises,
     * because browsers silently drop autoplay on a never-unlocked element.
     *
     * Reusing a single persistent element means only one unlock is required
     * (via `bindUserGesture()` or the first natural play-during-gesture), and
     * every later cache-hit play on the same element is permitted.
     *
     * The element is attached to `<body>` (hidden) because some mobile
     * browsers require attachment for `.play()` to dispatch correctly.
     *
     * @private
     */
    _getPooledAudioElement(): HTMLAudioElement;
    /**
     * Play from a URL using a **pooled** HTMLAudioElement.
     * On iOS/mobile, play() may reject if not called during a user gesture.
     * In that case we fire onEnd immediately so callers are never left hanging.
     * @private
     */
    _playFromUrl(url: string, { onEnd, onStart, onError, generation, text, refreshSafetyTimeoutFromMetadata }: AEPlayCallbacks): void;
    /**
     * Play a cached MIDI-like sequence using simple Web Audio oscillators.
     * @private
     */
    _playMidiSequence(sequence: Record<string, unknown>, { onEnd, onStart, onError, generation }: AEPlayCallbacks): Promise<void>;
    /**
     * Stop any audio currently being played through this engine.
     */
    stopPlayback(): void;
    /** @private — generation counter for speak(); independent of _playGeneration */
    _speakGeneration: number;
    /** @private — safety timeout for the SDK playKeepworkAudio fallback path */
    _speakTimeoutId: ReturnType<typeof setTimeout> | null;
    /** @private */
    _clearSpeakTimeout(): void;
    /**
     * Play TTS audio for `text`, trying cached AudioEngine playback first,
     * then falling back to the SDK `playKeepworkAudio` path.
     *
     * Handles generation-based cancellation so that calling `stopSpeak()` at
     * any point safely invalidates in-flight async SDK callbacks.
     *
     * @param {string} text
     * @param {object} [options]
     * @param {function} [options.onEnd]
     * @param {function} [options.onStart]
     * @param {function} [options.onError]
     * @returns {{ type: string }|null}
     */
    speak(text: string, options?: Record<string, unknown>): {
        type: string;
    } | null;
    /**
     * Stop all speech audio: engine playback, SDK-managed audio, and fallback keepwork-audio element.
     * Also invalidates any in-flight speak() callbacks via generation counter.
     */
    stopSpeak(): void;
    /** @private */
    _stopCurrentAudio(): void;
    /** @private */
    _setSafetyTimeout(safeOnEnd: (() => void) | null | undefined, timeoutMs: number, text: string): void;
    /** @private */
    _clearSafetyTimeout(): void;
    /** @private */
    _clearMidiEndTimer(): void;
    /**
     * Estimate audio playback duration from text length (ms).
     * Useful for setting timeouts when actual audio duration is unknown.
     */
    static estimateDuration(text: string): number;
    /**
     * Clear all in-memory caches (does NOT clear IndexedDB).
     */
    clearCache(): void;
    /**
     * Clear both in-memory caches and IndexedDB persistent cache.
     */
    clearAll(): Promise<void>;
    /**
     * Get cache statistics (for debugging).
     */
    getStats(): {
        total: number;
        ready: number;
        loading: number;
        error: number;
        blobCached: number;
        audioBufferDecoded: number;
        midiCached: number;
        persistentQueueSize: number;
        persistentRunning: boolean;
    };
}
export {};
//# sourceMappingURL=AudioEngine.d.ts.map