/** * Menu DOM builders for the desktop UI plugin. * * Menu-frame structure: * * menu-frame-dialog () * └─ menu-wrapper * └─ menu-frame (.open when any menu is showing) * └─ menu-content (.sub-menu-open when a sub-menu replaces main) * ├─ main-menu (back-button list of categories) * └─ sub-menu (slot that holds one of the sub-menu-content panes) * ├─ language-menu (sub-menu-content.is-open when active) * ├─ subtitle-menu * ├─ quality-menu * └─ speed-menu * * The plugin owns DOM listener cleanup via `this.listen(...)`, so each * builder takes a `listen` helper and a small action-callback bag. */ import type { IVideoPlayer, VideoPlaylistItem } from '../../../index.js'; export type MenuListen = (target: EventTarget, event: string, fn: (event: Event) => void) => void; export declare const SUB_MENU_ID: { readonly LANGUAGE: "language"; readonly SUBTITLES: "subtitles"; readonly QUALITY: "quality"; readonly SPEED: "speed"; readonly PLAYLIST: "playlist"; readonly SUBTITLE_SETTINGS: "subtitleSettings"; readonly ASPECT_RATIO: "aspectRatio"; }; export type SubMenuId = typeof SUB_MENU_ID[keyof typeof SUB_MENU_ID]; export interface MenuActions { closeMenu: () => void; openSubMenu: (id: SubMenuId) => void; backToMain: () => void; } /** Build the empty `` shell + main-menu + sub-menu containers. */ export interface MenuFrameRefs { frameDialog: HTMLDialogElement; frame: HTMLDivElement; content: HTMLDivElement; main: HTMLDivElement; sub: HTMLDivElement; /** * A map of sub-menu id → its sub-menu-content pane. The plugin * populates these on demand as data changes. */ panes: Record; /** * A map of sub-menu id → its corresponding main-menu button. * The plugin toggles `display: none` on these when a category has * no available options. */ mainButtons: Record; /** Container for `settingsMenuActions` rows — pass to `renderMainMenuActions` to repaint on a live options change. */ mainMenuActionsContainer: HTMLDivElement; } /** * Consumer-supplied toggle row for the settings main menu — the menu * counterpart of the `buttons` map. `label` resolves at render time so * i18n applies; `get`/`set` bind the row to wherever the state lives * (a plugin, app storage, ...). */ export interface SettingsToggleItem { id: string; label: () => string; get: () => boolean; set: (value: boolean) => void; } /** * Consumer-supplied action row — a generic "plain button" menu entry shared * by two extension points: `subtitleMenuActions` (subtitles sub-menu, e.g. * "Search subtitles online…") and `settingsMenuActions` (main settings menu, * e.g. "Cast to device…", opening the app's own device picker). The shape is * identical for both; only the mount point differs. Shown whenever provided * — for the subtitles sub-menu that means even with zero subtitle tracks, * since that's exactly when an external-search action matters most, so it is * never gated behind the subtitles button's usual content-visibility check. * `label` resolves at render time so i18n applies, matching * `SettingsToggleItem`. `onSelect` receives the player so the consumer can * read current state (item, language) before opening its own UI. */ export interface SubtitleMenuAction { id: string; label: () => string; /** Raw SVG markup for the row's icon slot, inserted as-is like the built-in icons. Omit for a text-only row. */ icon?: string; onSelect: (player: IVideoPlayer) => void | Promise; } export declare function buildMenuFrame(player: IVideoPlayer, parent: HTMLElement, listen: MenuListen, actions: MenuActions, settingsItems?: ReadonlyArray, settingsMenuActions?: ReadonlyArray): MenuFrameRefs; /** * (Re)paint the main-menu consumer action rows into `container`. Unlike the * static category buttons and the `settingsItems` toggle rows, this is * designed to be called more than once — a live `settingsMenuActions` change * (e.g. a Cast SDK becoming available after mount) calls this again with the * same container to refresh the rows in place, same pattern as * `renderSubsPane`'s `scroll.replaceChildren()` + re-`listen()` refresh. */ export declare function renderMainMenuActions(container: HTMLDivElement, settingsMenuActions: ReadonlyArray | undefined, listen: MenuListen, closeMenu: () => void, player: IVideoPlayer): void; export interface MenuRenderState { /** Active subtitle index, or -1 / null when subtitles are off. */ subtitleIdx: number | null; /** Active audio track index. -1 = none / default. */ audioIdx: number; /** Active quality level index, or `'auto'` for auto. */ qualityIdx: number | 'auto'; /** * Level the backend is actually playing right now. In Auto mode this * differs from `qualityIdx` (which stays `'auto'`); the menu surfaces it * as a lower-importance sublabel on the Auto row. Null before the first * `level-switched` event lands or for non-HLS sources. */ playingQualityIdx?: number | null; } export declare function renderSpeedPane(pane: HTMLDivElement, player: IVideoPlayer, listen: MenuListen, onPick: () => void): void; export declare function renderQualityPane(pane: HTMLDivElement, player: IVideoPlayer, listen: MenuListen, onPick: () => void, state: MenuRenderState): void; export declare function renderSubsPane(pane: HTMLDivElement, player: IVideoPlayer, listen: MenuListen, onPick: () => void, state: MenuRenderState, actions?: ReadonlyArray): void; export declare function renderSubtitleSettingsPane(pane: HTMLDivElement, player: IVideoPlayer, listen: MenuListen, onPick: () => void): void; /** * Optional image base — pass via `imageBaseUrl` if your playlist items * carry relative TMDB-style paths (e.g. `/w780/abc.jpg`). */ export interface PlaylistRenderOptions { imageBaseUrl?: string; } /** * Returns true only when the queue contains two or more distinct season * numbers >= 1 among non-movie items. Every other case (season-0 specials, * a single season, a movie collection) returns false and renders a flat list. */ export declare function shouldShowSeasonSidebar(queue: ReadonlyArray): boolean; /** * Playlist sub-menu — rich-card layout. * * Adaptive layout: * - Flat list: season-0 specials, a movie collection, or a single season — * the seasons rail is hidden and the episodes rail fills the full width. * - Seasonal (sidebar): two or more TV seasons >= 1 — the left rail shows * season buttons; clicking a season filters the right rail to that season. */ export declare function renderPlaylistPane(pane: HTMLDivElement, player: IVideoPlayer, listen: MenuListen, onPick: () => void, opts?: PlaylistRenderOptions): void; export declare function renderAudioPane(pane: HTMLDivElement, player: IVideoPlayer, listen: MenuListen, onPick: () => void, state: MenuRenderState): void; export declare const ASPECT_RATIO_VALUE: { readonly UNIFORM: "uniform"; readonly FILL: "fill"; readonly EXACTFIT: "exactfit"; readonly NONE: "none"; }; export type AspectRatioValue = typeof ASPECT_RATIO_VALUE[keyof typeof ASPECT_RATIO_VALUE]; export declare function renderAspectRatioPane(pane: HTMLDivElement, player: IVideoPlayer, listen: MenuListen, onPick: () => void): void;