/**
 * Core types shared across avbridge modules.
 *
 * The four main concepts:
 * - {@link MediaInput} — what the user gives us (File / Blob / URL / bytes).
 * - {@link MediaContext} — what we learned about it from probing.
 * - {@link Classification} — which playback strategy we picked.
 * - {@link PlaybackSession} — the running playback, returned by a strategy.
 */
/**
 * Anything we accept as a media source. We do not accept arbitrary
 * `ReadableStream`s in v1 because we need random access for seeking.
 */
type MediaInput = File | Blob | string | URL | ArrayBuffer | Uint8Array;
/** Container format families we know about. */
type ContainerKind = "mp4" | "mov" | "mkv" | "webm" | "avi" | "asf" | "flv" | "rm" | "ogg" | "wav" | "mp3" | "flac" | "adts" | "mpegts" | "unknown";
/** Video codec families. Strings, not enums, so plugins can extend. */
type VideoCodec = "h264" | "h265" | "vp8" | "vp9" | "av1" | "mpeg4" | "wmv3" | "vc1" | "rv10" | "rv20" | "rv30" | "rv40" | "mpeg2" | "mpeg1" | "theora" | "dv" | "hq_hqa" | "rawvideo" | "qtrle" | "png" | "vp6f" | (string & {});
/** Audio codec families. */
type AudioCodec = "aac" | "mp3" | "opus" | "vorbis" | "flac" | "pcm" | "ac3" | "eac3" | "wmav2" | "wmapro" | "alac" | "cook" | "ra_144" | "ra_288" | "sipr" | "atrac3" | "dts" | "truehd" | (string & {});
interface VideoTrackInfo {
    id: number;
    codec: VideoCodec;
    /** Codec-private profile string when known (e.g. "High", "Main 10"). */
    profile?: string;
    level?: number;
    width: number;
    height: number;
    /** Pixel format string in ffmpeg style (e.g. "yuv420p", "yuv420p10le"). */
    pixelFormat?: string;
    /** Frames per second, when known. */
    fps?: number;
    bitDepth?: number;
    /** RFC 6381 codec string for `MediaSource.isTypeSupported`, when computable. */
    codecString?: string;
}
interface AudioTrackInfo {
    id: number;
    codec: AudioCodec;
    channels: number;
    sampleRate: number;
    language?: string;
    codecString?: string;
}
interface SubtitleTrackInfo {
    id: number;
    /** "vtt" | "srt" | "ass" | "pgs" | "embedded" */
    format: string;
    language?: string;
    /** Set if this is a sidecar file rather than embedded in the container. */
    sidecarUrl?: string;
}
/**
 * Everything the probe layer learned about a source.
 * This is the input to the classification engine.
 */
interface MediaContext {
    source: MediaInput;
    /** Stable display name for diagnostics, if we have one. */
    name?: string;
    byteLength?: number;
    container: ContainerKind;
    videoTracks: VideoTrackInfo[];
    audioTracks: AudioTrackInfo[];
    subtitleTracks: SubtitleTrackInfo[];
    /** Which probe backend produced this context, for diagnostics. */
    probedBy: "mediabunny" | "libav" | "sniff";
    /** Total duration in seconds, if known. */
    duration?: number;
}
/**
 * The four playback strategies, ordered from lightest to heaviest:
 * - `"native"` — direct `<video>` playback (zero overhead)
 * - `"remux"` — repackage to fragmented MP4 via MSE (preserves hardware decode)
 * - `"hybrid"` — libav.js demux + WebCodecs hardware decode (for AVI/ASF/FLV with modern codecs)
 * - `"fallback"` — full WASM software decode via libav.js (universal, CPU-intensive)
 */
type StrategyName = "native" | "remux" | "hybrid" | "fallback";
/**
 * Classification outcome from the rules engine. Determines which strategy to use:
 * - `NATIVE` — browser plays this directly
 * - `REMUX_CANDIDATE` — codecs are native, container needs repackaging
 * - `HYBRID_CANDIDATE` — container needs libav demux, codecs are hardware-decodable
 * - `FALLBACK_REQUIRED` — codec has no browser decoder, WASM decode needed
 * - `RISKY_NATIVE` — might work natively but may stall (e.g. Hi10P, 4K120)
 */
type StrategyClass = "NATIVE" | "REMUX_CANDIDATE" | "HYBRID_CANDIDATE" | "FALLBACK_REQUIRED" | "RISKY_NATIVE";
/**
 * A live playback session created by a strategy. The {@link UnifiedPlayer}
 * delegates user-facing controls to whichever session is currently active.
 */
interface PlaybackSession {
    readonly strategy: StrategyName;
    play(): Promise<void>;
    pause(): void;
    seek(time: number): Promise<void>;
    setAudioTrack(id: number): Promise<void>;
    setSubtitleTrack(id: number | null): Promise<void>;
    /** Tear down everything: revoke object URLs, close decoders, etc. */
    destroy(): Promise<void>;
    /** Strategy-specific runtime stats merged into Diagnostics. */
    getRuntimeStats(): Record<string, unknown>;
    /** Current playback position in seconds. Used to capture position before strategy switch. */
    getCurrentTime(): number;
    /** Register a callback for unrecoverable errors that should trigger escalation. */
    onFatalError?(handler: (reason: string) => void): void;
}
interface DiagnosticsSnapshot {
    container: ContainerKind | "unknown";
    videoCodec?: VideoCodec;
    audioCodec?: AudioCodec;
    width?: number;
    height?: number;
    fps?: number;
    duration?: number;
    strategy: StrategyName | "pending";
    strategyClass: StrategyClass | "pending";
    reason: string;
    probedBy?: "mediabunny" | "libav" | "sniff";
    /**
     * Where the source is coming from. `"blob"` means File / Blob /
     * ArrayBuffer / Uint8Array (in-memory). `"url"` means an HTTP/HTTPS URL
     * being streamed via Range requests.
     */
    sourceType?: "blob" | "url";
    /**
     * Transport used to read the source. `"memory"` for in-memory blobs;
     * `"http-range"` for URL sources streamed via HTTP Range requests.
     */
    transport?: "memory" | "http-range";
    /**
     * For URL sources, true if the server supports HTTP Range requests
     * (the only mode we accept — see `attachLibavHttpReader`). Always true
     * when `transport === "http-range"` because we fail fast otherwise.
     */
    rangeSupported?: boolean;
    runtime?: Record<string, unknown>;
    strategyHistory?: Array<{
        strategy: StrategyName;
        reason: string;
        at: number;
    }>;
}
/** §8.2 plugin interface, kept structurally identical to the design doc. */
interface Plugin {
    name: string;
    canHandle(context: MediaContext): boolean;
    /** Returns a session if it claims the context, otherwise throws. */
    execute(context: MediaContext, target: HTMLVideoElement, transport?: TransportConfig): Promise<PlaybackSession>;
}
/** Player creation options. */
interface CreatePlayerOptions {
    source: MediaInput;
    target: HTMLVideoElement;
    /**
     * Optional explicit subtitle list. The player otherwise tries to discover
     * sidecar files via the FileSystemDirectoryHandle (when supplied), or pulls
     * embedded subtitle tracks if the container exposes them.
     */
    subtitles?: Array<{
        url: string;
        language?: string;
        format?: "vtt" | "srt";
    }>;
    /**
     * Optional directory handle for sidecar discovery. When the source is a
     * `File` selected from this directory, sibling `*.srt`/`*.vtt` files are
     * picked up automatically.
     */
    directory?: FileSystemDirectoryHandle;
    /**
     * Skip classification and start with the given strategy. Useful for
     * diagnostics, tests, and consumers that already know the right path.
     *
     * **Note:** this is the *initial* strategy, not a hard force — if the
     * named strategy fails to start, the player still walks the fallback
     * chain like a normal classification would. The strategy class shown in
     * diagnostics matches whatever the picked strategy actually is, not
     * "NATIVE" by default.
     */
    initialStrategy?: StrategyName;
    /** Inject extra plugins; they take priority over built-ins. */
    plugins?: Plugin[];
    /**
     * When true (default), the player automatically escalates to the next
     * strategy in the fallback chain on failure or stall.
     */
    autoEscalate?: boolean;
    /**
     * Behavior when the browser tab becomes hidden.
     * - `"pause"` (default): auto-pause on hide, auto-resume on visible
     *   if the user had been playing. Matches YouTube, Netflix, and
     *   native media players. Prevents degraded playback from Chrome's
     *   background throttling of requestAnimationFrame and setTimeout.
     * - `"continue"`: keep playing. Playback will degrade anyway due to
     *   browser throttling, but useful for consumers who want full
     *   control of visibility handling themselves.
     */
    backgroundBehavior?: "pause" | "continue";
    /**
     * Extra {@link RequestInit} merged into every HTTP request the player
     * makes (probe Range requests, subtitle fetches, libav HTTP reader).
     * Headers are merged, not overwritten — so you can add `Authorization`
     * without losing the player's `Range` header.
     */
    requestInit?: RequestInit;
    /**
     * Custom fetch implementation. Defaults to `globalThis.fetch`. Useful
     * for interceptors, logging, or environments without a global fetch.
     */
    fetchFn?: FetchFn;
    /**
     * Byte budget for the libav HTTP reader's LRU cache of fetched ranges.
     * Defaults to 8 MB. Set to `0` to disable caching. Raise this when the
     * app plays seek-heavy legacy-container media from URLs — hot regions
     * (header/moov, tail index, current window) stay resident instead of
     * being re-fetched on every bounce.
     */
    cacheBytes?: number;
}
/** Signature-compatible with `globalThis.fetch`. */
type FetchFn = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
/** Internal transport config bundle. Not part of the public API. */
interface TransportConfig {
    requestInit?: RequestInit;
    fetchFn?: FetchFn;
    /**
     * Byte budget for the libav HTTP reader's LRU cache of fetched ranges.
     * Defaults to 8 MB. Set to `0` to disable caching entirely. Higher
     * values help seek-heavy network playback keep hot regions
     * (header/moov, tail index, current read) resident.
     */
    cacheBytes?: number;
}
/** Events emitted by {@link UnifiedPlayer}. Strongly typed. */
interface PlayerEventMap {
    strategy: {
        strategy: StrategyName;
        reason: string;
    };
    strategychange: {
        from: StrategyName;
        to: StrategyName;
        reason: string;
        currentTime: number;
    };
    tracks: {
        video: VideoTrackInfo[];
        audio: AudioTrackInfo[];
        subtitle: SubtitleTrackInfo[];
    };
    error: Error;
    timeupdate: {
        currentTime: number;
    };
    ended: void;
    ready: void;
}
type PlayerEventName = keyof PlayerEventMap;
/** Generic listener type re-exported for player.on overloads. */
type Listener<T> = (payload: T) => void;
/**
 * CustomEvents dispatched by `<avbridge-video>` and `<avbridge-player>`.
 * Map each event name to the full `CustomEvent<Detail>` so addEventListener
 * overloads can type the listener parameter correctly.
 *
 * Standard HTMLMediaElement events (play, pause, seeking, volumechange,
 * etc.) are forwarded from the inner `<video>` as plain `Event`s and remain
 * typed via the built-in `HTMLElementEventMap`. They are NOT in this map —
 * TypeScript will pick up their native types automatically.
 */
interface AvbridgeVideoElementEventMap {
    strategychange: CustomEvent<{
        strategy: StrategyName;
        strategyClass: string | null;
        reason: string;
        from?: StrategyName;
        currentTime?: number;
        diagnostics: DiagnosticsSnapshot;
    }>;
    trackschange: CustomEvent<{
        audioTracks: AudioTrackInfo[];
        subtitleTracks: SubtitleTrackInfo[];
    }>;
    timeupdate: CustomEvent<{
        currentTime: number;
    }>;
    ended: CustomEvent<Record<string, never>>;
    ready: CustomEvent<{
        diagnostics: DiagnosticsSnapshot;
    }>;
    destroy: CustomEvent<Record<string, never>>;
    error: CustomEvent<{
        error: Error;
        diagnostics: DiagnosticsSnapshot | null;
    }>;
    progress: CustomEvent<{
        buffered: TimeRanges;
    }>;
    loadstart: CustomEvent<Record<string, never>>;
    fitchange: CustomEvent<{
        fit: "contain" | "cover" | "fill";
    }>;
}
/**
 * Configuration for a custom settings section added to `<avbridge-player>`
 * via {@link addSettingsSection}. Sections render in the bottom-sheet
 * settings panel alongside built-in sections (Speed, Audio, Subtitles,
 * Fit, Stats for Nerds). The player owns rendering — consumers describe
 * data; avbridge renders it in a consistent visual style.
 */
interface SettingsSectionConfig {
    /** Unique id for this section. Used to update/remove later. */
    id: string;
    /** Display label (e.g. "Quality", "Translate"). */
    label: string;
    /** Items to show when the section is expanded. */
    items: Array<{
        id: string;
        label: string;
        /** Mark the currently-selected item. */
        active?: boolean;
    }>;
    /** Called when the user picks an item. */
    onSelect(itemId: string): void;
}

/**
 * `<avbridge-player>` — YouTube-style controls element.
 *
 * Wraps `<avbridge-video>` with a full player UI: play/pause, seek bar,
 * volume, settings menu (speed, subtitles, audio tracks), fullscreen,
 * keyboard shortcuts, touch gestures, and auto-hiding controls.
 *
 * All properties, methods, and events from `<avbridge-video>` are proxied
 * through. Consumers interact with `<avbridge-player>` exclusively.
 */

declare class AvbridgePlayerElement extends HTMLElement {
    static readonly observedAttributes: ("src" | "muted" | "autoplay" | "loop" | "preload" | "poster" | "playsinline" | "crossorigin" | "disableremoteplayback" | "preferstrategy" | "fit" | "show-fit")[];
    /**
     * Returns `true` if a DOM event originated from one of the player's
     * **interactive chrome elements** (seek bar, control buttons, settings
     * menu, overlay play button) rather than the bare video surface.
     *
     * This is the escape hatch for host pages that wrap the player in a
     * gesture recognizer (e.g. TikTok-style vertical-swipe pager). For
     * bubble-phase listeners the player's own handlers already call
     * `stopPropagation()` on chrome interactions — but **capture-phase**
     * listeners run *before* the player's handlers, so they need to check
     * the event's path themselves and bail. This helper does that check
     * via `composedPath()`, which traverses shadow boundaries correctly.
     *
     * Returns `false` for events on the bare video surface — host pages
     * remain free to claim those for their own gestures (e.g. swipe-to-pan
     * to the next video). Returns `false` for events that never hit a
     * player at all.
     *
     * @example
     * // TikTok-style vertical swipe on the document, capture phase:
     * document.addEventListener("pointerdown", (e) => {
     *   if (AvbridgePlayerElement.isPlayerChromeEvent(e)) return;
     *   startSwipeGesture(e);
     * }, { capture: true });
     */
    static isPlayerChromeEvent(event: Event): boolean;
    private _video;
    private _playBtn;
    private _overlayBtn;
    private _seekInput;
    private _seekProgress;
    private _seekBuffered;
    private _seekThumb;
    private _seekTooltip;
    private _timeDisplay;
    private _volumeBtn;
    private _volumeInput;
    private _settingsBtn;
    private _settingsMenu;
    private _settingsScrim;
    private _customSections;
    private _fullscreenBtn;
    private _speedIndicator;
    private _rippleLeft;
    private _rippleRight;
    private _state;
    private _controlsTimer;
    private _settingsOpen;
    private _activeAudioTrackId;
    private _activeSubtitleTrackId;
    private _userSeeking;
    /** Last seek target the user committed. The thumb stays here (and
     *  `_updateTime` skips updating from `timeupdate`) until the underlying
     *  `currentTime` actually catches up — otherwise the thumb visibly snaps
     *  back to the pre-seek position while the remux pipeline rebuilds. */
    private _pendingSeekTarget;
    private _holdTimer;
    private _holdSpeedActive;
    private _savedPlaybackRate;
    private _lastTapTime;
    private _tapTimer;
    private _statsOpen;
    private _statsEl;
    private _statsInterval;
    private _eventCleanup;
    private _updateToolbarEmpty;
    private _toolbarTop;
    constructor();
    private _template;
    private _bindEvents;
    connectedCallback(): void;
    disconnectedCallback(): void;
    attributeChangedCallback(name: string, _old: string | null, value: string | null): void;
    private _setState;
    private _togglePlay;
    private _onSeekInput;
    private _onSeekCommit;
    /** Linear click-to-time mapping across the full track width (no edge clamping). */
    private _timeFromSeekPointer;
    private _onSeekPointerDown;
    private _onSeekHover;
    private _updateSeekTooltip;
    /** Position the tooltip over a specific time (vs. pointer X). Used by
     *  relative-drag scrub on coarse pointers, where the displayed time
     *  is decoupled from the finger position. */
    private _updateSeekTooltipAtTime;
    private _updateSeekVisuals;
    private _updateTime;
    /**
     * Render every buffered range as its own segment so gaps (common on MSE
     * after seeks) are visible. Not gated by `_userSeeking` — ranges should
     * keep updating while the user scrubs, and runs cheaply on `progress`.
     */
    private _updateBuffered;
    private _toggleMute;
    private _updateVolume;
    private _toggleSettings;
    private _fitSettingsToPlayer;
    private _closeSettings;
    private _buildSettingsMenu;
    private _statsPrev;
    private _toggleStats;
    private _updateStats;
    private _toggleFullscreen;
    private _updateFullscreenIcon;
    /**
     * Reveal the auto-hiding chrome (top toolbar + bottom controls) and
     * re-start the auto-hide timer. Call this from app-level code to
     * briefly surface the player UI — e.g. to confirm "you just swiped to
     * this video" in a carousel, or to flash the title on focus change.
     *
     * @param durationMs  How long the chrome stays visible before fading.
     *                    Defaults to the player's normal 3 s auto-hide.
     *                    Pointer movement or any other interaction resets
     *                    the timer, so a user hovering during the flash
     *                    sees no flicker.
     */
    showControls(durationMs?: number): void;
    private _showControls;
    private _scheduleHide;
    /** Read the controls-timeout attribute. 0 or negative = never hide.
     *  Unset = default 3000ms. */
    private _getControlsTimeout;
    /** Track whether the last interaction was touch so click handler can skip. */
    private _lastPointerTypeWasTouch;
    /** True for ~50ms after a touch double-tap was handled, so the
     *  synthetic dblclick from the browser doesn't also fire fullscreen. */
    private _touchDoubleTapConsumed;
    /** True if the event's composed path passes through consumer-slotted
     *  content (toolbar or content-overlay). Slotted content lives in the
     *  light DOM so `.closest(".avp-toolbar-top")` on the event target won't
     *  find the shadow-DOM wrapper — `composedPath()` does. */
    private _isSlottedContentEvent;
    private _onContainerClick;
    private _onContainerDblClick;
    private _onPointerDown;
    private _onPointerUp;
    private _cancelHold;
    private _doDoubleTap;
    /** Duration of one frame in seconds, derived from diagnostics fps or
     *  a 30fps default. Used for frame-step shortcuts (`,` / `.`). */
    private _frameDuration;
    private _onKeydown;
    private _clearTimers;
    get src(): string;
    set src(v: string);
    get source(): unknown;
    set source(v: unknown);
    get currentTime(): number;
    set currentTime(v: number);
    get duration(): number;
    get paused(): boolean;
    get ended(): boolean;
    get readyState(): number;
    get volume(): number;
    set volume(v: number);
    get muted(): boolean;
    set muted(v: boolean);
    get playbackRate(): number;
    set playbackRate(v: number);
    get autoplay(): boolean;
    set autoplay(v: boolean);
    get loop(): boolean;
    set loop(v: boolean);
    get videoWidth(): number;
    get videoHeight(): number;
    get buffered(): TimeRanges;
    get played(): TimeRanges;
    get seekable(): TimeRanges;
    get strategy(): string | undefined;
    get strategyClass(): string | undefined;
    get audioTracks(): unknown[];
    get subtitleTracks(): unknown[];
    /**
     * External subtitle files to attach when the source loads. Forwarded
     * to the inner <avbridge-video>. Takes effect on next bootstrap.
     */
    get subtitles(): unknown;
    set subtitles(value: unknown);
    /** Attach a subtitle track to the current playback without a reload. */
    addSubtitle(subtitle: {
        url: string;
        language?: string;
        format?: "vtt" | "srt";
    }): Promise<void>;
    get player(): unknown;
    get videoElement(): HTMLVideoElement;
    play(): Promise<void>;
    pause(): void;
    load(): Promise<void>;
    destroy(): Promise<void>;
    setAudioTrack(id: number): Promise<void>;
    addSettingsSection(config: SettingsSectionConfig): void;
    removeSettingsSection(id: string): void;
    setSubtitleTrack(id: number | null): Promise<void>;
    getDiagnostics(): unknown;
    canPlayType(mime: string): string;
    addEventListener<K extends keyof AvbridgeVideoElementEventMap>(type: K, listener: (this: AvbridgePlayerElement, ev: AvbridgeVideoElementEventMap[K]) => unknown, options?: boolean | AddEventListenerOptions): void;
    addEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: AvbridgePlayerElement, ev: HTMLElementEventMap[K]) => unknown, options?: boolean | AddEventListenerOptions): void;
    addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
    removeEventListener<K extends keyof AvbridgeVideoElementEventMap>(type: K, listener: (this: AvbridgePlayerElement, ev: AvbridgeVideoElementEventMap[K]) => unknown, options?: boolean | EventListenerOptions): void;
    removeEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: AvbridgePlayerElement, ev: HTMLElementEventMap[K]) => unknown, options?: boolean | EventListenerOptions): void;
    removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
}

declare class UnifiedPlayer {
    private readonly options;
    private readonly registry;
    private emitter;
    private session;
    private diag;
    private timeupdateInterval;
    private mediaContext;
    private classification;
    private stallTimer;
    private lastProgressTime;
    private lastProgressPosition;
    /** Last observed `HTMLVideoElement.getVideoPlaybackQuality().totalVideoFrames`
     *  (or `webkitDecodedFrameCount` fallback). Used by the silent-video
     *  watchdog — catches cases where `currentTime` advances (audio plays)
     *  but the decoder produces no frames, e.g. Firefox claiming `hev1.*`
     *  via MSE when the decoder actually can't decode HEVC. */
    private lastVideoFrameCount;
    private lastVideoFrameProgressTime;
    private errorListener;
    private endedListener;
    private userIntent;
    private autoPausedForVisibility;
    private visibilityListener;
    private switchingPromise;
    private subtitleResources;
    private readonly transport;
    /**
     * @internal Use {@link createPlayer} or {@link UnifiedPlayer.create} instead.
     */
    private constructor();
    static create(options: CreatePlayerOptions): Promise<UnifiedPlayer>;
    private bootstrap;
    /**
     * Try to start a session with the given strategy. On failure, walk the
     * fallback chain. Throws only if all strategies are exhausted.
     */
    private startSession;
    private escalate;
    private doEscalate;
    private attachSupervisor;
    private clearSupervisor;
    /** Manually switch to a different playback strategy. Preserves current position and play/pause state. Concurrent calls are serialized. */
    setStrategy(strategy: StrategyName, reason?: string): Promise<void>;
    private doSetStrategy;
    private startTimeupdateLoop;
    /** Subscribe to a player event. Returns an unsubscribe function. Sticky events (strategy, ready, tracks) replay for late subscribers. */
    on<K extends PlayerEventName>(event: K, fn: Listener<PlayerEventMap[K]>): () => void;
    /** Remove a previously registered event listener. */
    off<K extends PlayerEventName>(event: K, fn: Listener<PlayerEventMap[K]>): void;
    /** Begin or resume playback. Throws if the player is not ready. */
    play(): Promise<void>;
    /** Pause playback. No-op if the player is not ready or already paused. */
    pause(): void;
    /**
     * Handle browser tab visibility changes. On hide: pause if the user
     * had been playing. On show: resume if we were the one who paused.
     * Skips when `backgroundBehavior: "continue"` is set (listener isn't
     * installed in that case).
     */
    private onVisibilityChange;
    /** Seek to the given time in seconds. Throws if the player is not ready. */
    seek(time: number): Promise<void>;
    /** Switch the active audio track by track ID. Throws if the player is not ready. */
    setAudioTrack(id: number): Promise<void>;
    /** Switch the active subtitle track by track ID, or pass `null` to disable subtitles. */
    setSubtitleTrack(id: number | null): Promise<void>;
    /** Return a snapshot of current diagnostics: container, codecs, strategy, runtime stats, and strategy history. */
    getDiagnostics(): DiagnosticsSnapshot;
    /** Return the total duration in seconds, or `NaN` if unknown. */
    getDuration(): number;
    /** Return the current playback position in seconds. */
    getCurrentTime(): number;
    /** Tear down the player: stop timers, destroy the active session, remove all event listeners. The player is unusable after this call. */
    destroy(): Promise<void>;
}

/**
 * `<avbridge-video>` — `HTMLMediaElement`-compatible primitive backed by the
 * avbridge engine. Drop-in replacement for a `<video>` element with no
 * built-in UI.
 *
 * Purpose:
 *
 *   1. Validate the public API by being a real consumer of `createPlayer()`.
 *   2. Drive lifecycle correctness in the core via adversarial integration tests.
 *   3. Give consumers a `<video>`-compatible primitive they can wrap with
 *      their own UI.
 *
 * **It is not a player UI framework.** For YouTube-style chrome (seek
 * bar, play/pause, settings menu, fullscreen, auto-hiding controls) use
 * `<avbridge-player>` — it wraps this element with a full UI. See
 * `docs/dev/WEB_COMPONENT_SPEC.md` for the full spec, lifecycle invariants,
 * and edge case list.
 */

/** Strategy preference passed via the `preferstrategy` attribute. */
type PreferredStrategy = "auto" | StrategyName;
/** Fit mode — how the video fills the element's box. Mirrors CSS object-fit. */
type FitMode = "contain" | "cover" | "fill";
/**
 * `HTMLElement` is a browser-only global. SSR frameworks (Next.js, Astro,
 * Remix, etc.) commonly import library modules on the server to extract
 * types or do tree-shaking, even if the user only ends up using them in
 * the browser. If we extended `HTMLElement` directly, the `class extends`
 * expression would be evaluated at module load time and crash in Node.
 *
 * The fix: in non-browser environments, fall back to an empty stub class.
 * The element is never *constructed* server-side (the registration in
 * `element.ts` is guarded by `typeof customElements !== "undefined"`), so
 * the stub is never instantiated — it just lets the class declaration
 * evaluate cleanly so the module can be imported anywhere.
 */
declare const HTMLElementCtor: typeof HTMLElement;
/**
 * Custom element. Lifecycle correctness is enforced via a monotonically
 * increasing `_bootstrapId`: every async bootstrap captures the ID at start
 * and discards itself if the ID has changed by the time it resolves. This
 * single pattern handles disconnect-during-bootstrap, rapid src reassignment,
 * bootstrap races, and destroy-during-bootstrap.
 */
declare class AvbridgeVideoElement extends HTMLElementCtor {
    static readonly observedAttributes: string[];
    /** The shadow DOM `<video>` element that strategies render into. */
    private _videoEl;
    /** Active player session, if any. Cleared on teardown. */
    private _player;
    /**
     * Monotonic counter incremented on every (re)bootstrap. Async bootstrap
     * work captures the current ID; if it doesn't match by the time the work
     * resolves, the work is discarded.
     */
    private _bootstrapId;
    /** True after destroy() — element is permanently unusable. */
    private _destroyed;
    /** Internal source state. Either string-form (src) OR rich (source). */
    private _src;
    private _source;
    /**
     * Set when the `source` property setter is in the middle of clearing the
     * `src` attribute as part of mutual exclusion. The attributeChangedCallback
     * checks this flag and skips its normal "clear source" side effect, which
     * would otherwise wipe the value we just set.
     */
    private _suppressSrcAttrCallback;
    /** Last-known runtime state surfaced via getters. */
    private _strategy;
    private _strategyClass;
    private _audioTracks;
    /** Subtitle tracks reported by the active UnifiedPlayer (options.subtitles
     *  + embedded container tracks + programmatic addSubtitle calls). */
    private _subtitleTracks;
    /** Subtitle tracks derived from light-DOM `<track>` children. Maintained
     *  by _syncTextTracks on every mutation. Merged into the public
     *  `subtitleTracks` getter so the player's settings menu sees them. */
    private _htmlTrackInfo;
    /**
     * External subtitle list forwarded to `createPlayer()` on the next
     * bootstrap. Setting this after bootstrap queues it for the next
     * source change; consumers that need to swap subtitles mid-playback
     * should set `source` to reload.
     */
    private _subtitles;
    /**
     * Initial strategy preference. `"auto"` means "let the classifier decide";
     * any other value is passed to `createPlayer({ initialStrategy })` and
     * skips classification on the next bootstrap. Note that this only affects
     * the *initial* pick — runtime fallback escalation still applies, so a
     * preference of `"native"` may still escalate to remux/hybrid/fallback if
     * native fails.
     */
    private _preferredStrategy;
    /** Current fit mode. Applied to the inner `<video>` via object-fit, and
     *  to the fallback canvas via the `--avbridge-fit` CSS custom property on
     *  the stage wrapper (see `src/strategies/fallback/video-renderer.ts`). */
    private _fit;
    /** The stage wrapper — the element the canvas attaches into, and where
     *  the `--avbridge-fit` CSS custom property lives. */
    private _stageEl;
    /** Set if currentTime was assigned before the player was ready. */
    private _pendingSeek;
    /** Set if play() was called before the player was ready. */
    private _pendingPlay;
    /** MutationObserver tracking light-DOM `<track>` children. */
    private _trackObserver;
    /** Document-level fullscreenchange handler — installed while connected so
     *  the element can lock/unlock screen orientation to match the video's
     *  intrinsic aspect. */
    private _fullscreenChangeHandler;
    /** True if we successfully called screen.orientation.lock() on the last
     *  fullscreen entry. Used to know whether to unlock on exit. */
    private _orientationLocked;
    constructor();
    connectedCallback(): void;
    disconnectedCallback(): void;
    attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null): void;
    /** Returns the currently-active source (src or source), whichever is set. */
    private _activeSource;
    /**
     * Mirror light-DOM `<track>` children into the shadow `<video>` so that
     * the browser's native text-track machinery picks them up. Called on
     * connect, on every mutation of light-DOM children, and once after each
     * source change so newly-set tracks survive a fresh `<video>`.
     *
     * Strategy: clone the children. We don't move them because the user's
     * code may still hold references to the originals (e.g. to set `default`).
     * The shadow copies are throwaway — we wipe them on every sync.
     */
    private _syncTextTracks;
    /** Internal src setter — separate from the property setter so the
     * attributeChangedCallback can use it without re-entering reflection. */
    private _setSrcInternal;
    /** Called whenever the active source changes (src or source). */
    private _onSourceChanged;
    private _bootstrap;
    /**
     * Tear down the active player and reset runtime state. Idempotent.
     * If `currentBootstrapId` is provided, the bootstrap counter is NOT
     * incremented (used by `_bootstrap()` to avoid invalidating itself).
     */
    private _teardown;
    get src(): string | null;
    set src(value: string | null);
    get source(): MediaInput | null;
    set source(value: MediaInput | null);
    get autoplay(): boolean;
    set autoplay(value: boolean);
    get muted(): boolean;
    set muted(value: boolean);
    get loop(): boolean;
    set loop(value: boolean);
    get preload(): "none" | "metadata" | "auto";
    set preload(value: "none" | "metadata" | "auto");
    get diagnostics(): boolean;
    set diagnostics(value: boolean);
    get fit(): FitMode;
    set fit(value: FitMode);
    get preferredStrategy(): PreferredStrategy;
    set preferredStrategy(value: PreferredStrategy);
    get currentTime(): number;
    set currentTime(value: number);
    get duration(): number;
    get paused(): boolean;
    get ended(): boolean;
    get readyState(): number;
    /**
     * Buffered time ranges for the active source. Mirrors the standard
     * `<video>.buffered` `TimeRanges` API.
     *
     * - **Native / remux:** pass-through to the real `<video>.buffered`
     *   (reflects the browser's SourceBuffer / progressive-download state).
     * - **Hybrid / fallback:** a single `[0, frontier]` range synthesized
     *   from the demuxer's read progress — "how far libav has ever pumped
     *   packets through." Monotonic; does not shrink on seek. This is an
     *   approximation, not MSE-fidelity: decoded frames on canvas strategies
     *   are consumed in flight, so we can't report per-range availability
     *   the way MSE does. Enough for a seek-bar buffered indicator.
     */
    get buffered(): TimeRanges;
    get poster(): string;
    set poster(value: string);
    get volume(): number;
    set volume(value: number);
    get playbackRate(): number;
    set playbackRate(value: number);
    get videoWidth(): number;
    get videoHeight(): number;
    get played(): TimeRanges;
    get seekable(): TimeRanges;
    get crossOrigin(): string | null;
    set crossOrigin(value: string | null);
    get disableRemotePlayback(): boolean;
    set disableRemotePlayback(value: boolean);
    /**
     * Native `HTMLMediaElement.canPlayType()` passthrough. Note that this
     * answers about the *browser's* native support, not avbridge's full
     * capabilities — avbridge can play many formats this method returns ""
     * for, by routing them to the remux/hybrid/fallback strategies.
     */
    canPlayType(mimeType: string): CanPlayTypeResult;
    /**
     * **Escape hatch.** The underlying shadow-DOM `<video>` element.
     *
     * Use for native browser APIs the wrapper doesn't expose:
     * - `el.videoElement.requestPictureInPicture()`
     * - `el.videoElement.audioTracks` (browser native, not avbridge's track list)
     * - direct integration with libraries that need a real HTMLVideoElement
     *
     * **Caveat:** When the active strategy is `"fallback"` or `"hybrid"`,
     * frames are rendered to a canvas overlay, not into this `<video>`.
     * APIs that depend on the actual pixels (Picture-in-Picture, captureStream)
     * will not show the playing content in those modes. Check `el.strategy`
     * before using such APIs.
     */
    get videoElement(): HTMLVideoElement;
    get strategy(): StrategyName | null;
    get strategyClass(): StrategyClass | null;
    get player(): UnifiedPlayer | null;
    get audioTracks(): AudioTrackInfo[];
    get subtitleTracks(): SubtitleTrackInfo[];
    /**
     * External subtitle files to attach when the source loads. Takes effect
     * on the next bootstrap — set before assigning `source`, or reload via
     * `load()` after changing. For dynamic post-bootstrap addition, use
     * `addSubtitle()` instead.
     *
     * @example
     * el.subtitles = [{ url: "/en.srt", format: "srt", language: "en" }];
     * el.src = "/movie.mp4";
     */
    get subtitles(): Array<{
        url: string;
        language?: string;
        format?: "vtt" | "srt";
    }> | null;
    set subtitles(value: Array<{
        url: string;
        language?: string;
        format?: "vtt" | "srt";
    }> | null);
    /**
     * Attach a subtitle track to the current playback without rebuilding
     * the player. Works while the element is playing — converts SRT to
     * VTT if needed, adds a `<track>` to the inner `<video>`. Canvas
     * strategies pick up the new track via their textTracks watcher.
     */
    addSubtitle(subtitle: {
        url: string;
        language?: string;
        format?: "vtt" | "srt";
    }): Promise<void>;
    /**
     * Disable the automatic `screen.orientation.lock()` that runs on
     * fullscreen entry. Set when you want to honor the device's native
     * auto-rotate instead of matching the video's intrinsic orientation.
     */
    get noOrientationLock(): boolean;
    set noOrientationLock(value: boolean);
    /** Called whenever `document.fullscreenchange` fires. If this element (or
     *  any of its ancestors) is now fullscreen, derive the target orientation
     *  from the video's intrinsic size and call `screen.orientation.lock()`.
     *  On exit, release the lock we took. iOS Safari rejects `lock()` — we
     *  swallow the rejection so nothing breaks on that path. */
    private _onFullscreenChange;
    /** Walk composed-tree ancestors to see if `target` is this element or
     *  any ancestor across shadow boundaries. `Node.contains()` can't cross
     *  shadow roots, so when `<avbridge-player>` (the fullscreen element)
     *  hosts this `<avbridge-video>` inside its shadow DOM, `contains()`
     *  returns false. */
    private _isInsideOrEquals;
    /** Derive "landscape" / "portrait" from the intrinsic video dimensions.
     *  Returns null when dimensions aren't known yet or the video is square.
     *  Uses `videoWidth` / `videoHeight` from the inner `<video>`, which the
     *  browser sets to the display-aspect-corrected size (so anamorphic
     *  content is judged by its display aspect, not pixel aspect). */
    private _desiredOrientation;
    /** Attempt to lock screen orientation. Swallows rejections — iOS Safari
     *  doesn't implement `lock()`, and desktop / non-fullscreen contexts will
     *  reject too. Records success so we know whether to unlock on exit. */
    private _lockOrientation;
    private _releaseOrientationLock;
    /** Force a (re-)bootstrap if a source is currently set. */
    load(): Promise<void>;
    /**
     * Begin or resume playback. If the player isn't ready yet, the call is
     * queued and applied once `ready` fires.
     */
    play(): Promise<void>;
    pause(): void;
    /**
     * Tear down the element permanently. After destroy(), the element ignores
     * all method calls and attribute changes.
     */
    destroy(): Promise<void>;
    setAudioTrack(id: number): Promise<void>;
    setSubtitleTrack(id: number | null): Promise<void>;
    getDiagnostics(): DiagnosticsSnapshot | null;
    addEventListener<K extends keyof AvbridgeVideoElementEventMap>(type: K, listener: (this: AvbridgeVideoElement, ev: AvbridgeVideoElementEventMap[K]) => unknown, options?: boolean | AddEventListenerOptions): void;
    addEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: AvbridgeVideoElement, ev: HTMLElementEventMap[K]) => unknown, options?: boolean | AddEventListenerOptions): void;
    addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
    removeEventListener<K extends keyof AvbridgeVideoElementEventMap>(type: K, listener: (this: AvbridgeVideoElement, ev: AvbridgeVideoElementEventMap[K]) => unknown, options?: boolean | EventListenerOptions): void;
    removeEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: AvbridgeVideoElement, ev: HTMLElementEventMap[K]) => unknown, options?: boolean | EventListenerOptions): void;
    removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
    private _dispatch;
    private _dispatchError;
}
declare global {
    interface HTMLElementTagNameMap {
        "avbridge-video": AvbridgeVideoElement;
    }
}

export { AvbridgePlayerElement, AvbridgeVideoElement };
