/**
 * Gap-tolerant audio renderer (Web Audio side).
 *
 * Core idea: keep a `nextPlayTime` cursor on the AudioContext clock. Every
 * decoded frame is scheduled at `nextPlayTime`, then the cursor advances by the
 * frame's duration. If the network stalls, `nextPlayTime` falls into the past;
 * resuming at `currentTime` makes late audio play immediately instead of piling
 * up.
 *
 * Decoding lives in the audio decoder Web Worker (src/decode/audio_decoder.ts);
 * this class only turns already-decoded AudioData frames into scheduled playback.
 * The worker hands each frame together with the true source timestamp (`ts`) of
 * the chunk that produced it, which is used to anchor media time on a resume.
 */
export interface PlayerOptions {
    sampleRate: number;
    numberOfChannels: number;
    /** Timestamp ticks per second used by the source/encoder (WebCodecs: 1e6 for µs). */
    timebase: number;
    /** Cushion (seconds) added when starting fresh or recovering from a gap. */
    jitterDelay: number;
    /** Apply a short fade-in on each resume to avoid clicks at gap edges. */
    fadeIn: boolean;
    onStats?: (s: PlayerStats) => void;
    onLog?: (msg: string) => void;
}
export interface PlayerStats {
    ctxState: AudioContextState;
    currentTime: number;
    nextPlayTime: number;
    bufferAhead: number;
    rendered: number;
    gapsRecovered: number;
    /** Media time in ms of the sample currently at the speakers, or null during silence. */
    playingTimestampTsMs: number | null;
}
export declare class GapTolerantPlayer {
    private ctx;
    private nextPlayTime;
    private lastBufferDuration;
    private opts;
    /**
     * Playback rate control. `setPlaybackSpeed` only records the request; it is
     * adopted by `addFrame` at the moment the next frame is scheduled (at the
     * nextPlayTime boundary), which keeps playback contiguous — see setPlaybackSpeed.
     */
    private requestedPlaybackSpeed;
    private currentPlaybackSpeed;
    private rendered;
    private gapsRecovered;
    private statsTimer;
    /**
     * Anchor for the current contiguous segment (the run since the last gap): the
     * media timestamp (in source timebase) of its first sample and the
     * AudioContext time it starts playing. Within a segment the media↔clock
     * mapping is linear, so the sounding timestamp is just anchorTs + (samples
     * played since the anchor).
     */
    private anchorTs;
    private anchorCtxStart;
    /** Playback speed the current segment plays at, so the media↔clock slope is right. */
    private anchorSpeed;
    /**
     * Buffer sources scheduled but not yet finished. Tracked so forceGap() can
     * stop still-pending audio when the jitter cushion is lowered live, otherwise
     * the re-primed (shorter) cursor would overlap audio already scheduled ahead.
     */
    private activeSources;
    constructor(opts: PlayerOptions);
    /** AudioContext sample rate actually granted by the browser (for the 1:1 guard). */
    get sampleRate(): number;
    /** System (output + base) latency in ms, for latency readouts. */
    get systemLatencyMs(): number;
    /** Live-tune the jitter cushion applied on the next gap recovery. */
    setJitterDelay(seconds: number): void;
    /**
     * Force a fresh segment on the next fed frame so a changed jitterDelay takes
     * effect immediately instead of waiting for the next network gap. The cushion
     * is only (re)applied when a segment starts, so we stop everything still
     * scheduled (preventing overlap when the cushion shrinks) and reset the
     * cursor; the next addFrame then re-primes exactly like the first frame —
     * re-padding with jitterDelay, re-anchoring media time, and fading in.
     */
    forceGap(): void;
    /** Toggle the anti-click fade-in for subsequently scheduled resumes. */
    setFadeIn(enabled: boolean): void;
    /**
     * Request a playback rate in [0.5, 2]. This only records the request: it does
     * NOT retune sources already scheduled, because changing playbackRate on a
     * playing node shifts when it ends but not the fixed start time of the node
     * after it, which would open a gap or overlap. Instead addFrame adopts the
     * requested speed when it schedules the next frame — at the nextPlayTime
     * boundary where the previous audio cleanly ends — so playback stays
     * contiguous. Note: playbackRate also shifts pitch (tape-speed behavior).
     */
    setPlaybackSpeed(speed: number): void;
    /** The last speed passed to setPlaybackSpeed (may still be pending). */
    getRequestedPlaybackSpeed(): number;
    /**
     * Speed adopted by the most recently scheduled frame. Equals the requested
     * value once a frame has been scheduled after the change; it trails while a
     * request is pending (e.g. the feed is stalled). Already-queued audio still
     * finishes at the rate it was scheduled with, so the audible speed can lag
     * this by up to bufferAhead while that queue drains.
     */
    getCurrentPlaybackSpeed(): number;
    /** Must be called from a user gesture so the browser unblocks audio. */
    resume(): Promise<void>;
    /**
     * Schedule one decoded frame. `ts` is the true source timestamp (in the
     * configured timebase) of the chunk that produced this frame; it re-anchors
     * media time whenever a new contiguous segment begins.
     */
    addFrame(audioData: AudioData, ts: number): void;
    private currentPlayingTimeMs;
    /**
     * Resolve once every buffered sample has finished playing: wait out the
     * remaining scheduled audio. Lets the caller drain before closing, so the
     * playing timestamp reaches the stream end instead of freezing on the tail.
     */
    whenDrained(): Promise<void>;
    private startStatsLoop;
    private log;
    close(): Promise<void>;
}
//# sourceMappingURL=audio_player.d.ts.map