import type { Frame, Page } from "puppeteer-core"; import type { Nullable, ResolvedSiteProfile, TuneResult, VideoSelectorType } from "../types/index.js"; /** * Builds a selector type identifier for the video element based on the site profile. This returns a string that browser context code interprets to select the * appropriate video element. Using a string identifier instead of passing functions avoids serialization issues with page.evaluate() and is more secure than * eval()-based approaches. * @param profile - The site profile indicating video selection strategy. * @returns A selector type identifier: "selectReadyVideo" for sites with multiple videos, "selectFirstVideo" for standard sites. */ export declare function buildVideoSelectorType(profile: ResolvedSiteProfile): VideoSelectorType; /** * Video state information returned by getVideoState(). Contains all properties needed to assess playback health. */ export interface VideoStateInfo { currentTime: number; ended: boolean; error: boolean; muted: boolean; networkState: number; paused: boolean; readyState: number; volume: number; } /** * Gets the current state of the video element for health monitoring. Returns null if no video element is found. * @param context - The frame or page containing the video element. * @param selectorType - The video selector type for finding the element. * @returns The video state or null if no video found. */ export declare function getVideoState(context: Frame | Page, selectorType: VideoSelectorType): Promise>; /** * Enforces volume settings on the video element. Sets muted to false and volume to 1. This is called periodically during health monitoring to counter sites that * aggressively mute videos. * @param context - The frame or page containing the video element. * @param selectorType - The video selector type for finding the element. */ export declare function enforceVideoVolume(context: Frame | Page, selectorType: VideoSelectorType): Promise; /** * Validation result for checking if a video element exists and is accessible. */ export interface VideoValidationResult { found: boolean; readyState?: number; } /** * Validates that a video element exists and returns its ready state. Used after page navigation to verify recovery succeeded. * @param context - The frame or page containing the video element. * @param selectorType - The video selector type for finding the element. * @returns Validation result indicating if video was found and its readyState. */ export declare function validateVideoElement(context: Frame | Page, selectorType: VideoSelectorType): Promise; /** * Result from checking video presence, distinguishing between "no video exists" and "video exists but not ready". */ export interface VideoPresenceResult { anyVideoExists: boolean; maxReadyState?: number; readyVideoFound: boolean; videoCount: number; } /** * Checks video presence in the context, returning detailed information about what videos exist and their states. This helps distinguish between: * - No video element exists at all (DOM issue, wrong context) * - Video elements exist but none are ready (buffering, still loading) * - Ready video exists (normal operation) * * This is useful when getVideoState returns null to determine if we should wait (video buffering) or escalate (no video at all). * @param context - The frame or page containing the video element. * @param selectorType - The video selector type for finding the element. * @returns Detailed presence information. */ export declare function checkVideoPresence(context: Frame | Page, selectorType: VideoSelectorType): Promise; /** * Reloads the video source to force the player to reinitialize. This clears the src attribute, calls load() to reset the player state, restores the original src, * and calls load() again. This is more disruptive than seeking but can fix players stuck in error states or with corrupted internal state. * @param context - The frame or page containing the video element. * @param selectorType - The video selector type for finding the element. */ export declare function reloadVideoSource(context: Frame | Page, selectorType: VideoSelectorType): Promise; /** * Starts video playback by ensuring the video is unmuted, at full volume, and playing. This combines volume enforcement with play() initiation for efficient single * round-trip execution in the browser context. * @param context - The frame or page containing the video element. * @param selectorType - The video selector type for finding the element. */ export declare function startVideoPlayback(context: Frame | Page, selectorType: VideoSelectorType): Promise; /** * Navigates a browser page to the specified URL with site-appropriate wait conditions. The navigation strategy depends on the site's player implementation: * * - waitForNetworkIdle=true: Wait for network activity to settle (no requests for 500ms). This ensures all JavaScript has loaded and the player is fully * initialized. Used for sites with complex async initialization. * * - waitForNetworkIdle=false: Return as soon as the page fires load event. Used for sites that have persistent connections or polling that would prevent * networkidle from ever completing. * * Navigation timeouts are handled gracefully - we log a warning but don't throw, since the video may have loaded successfully even if networkidle never * completed. * @param page - The Puppeteer page object. * @param url - The URL to navigate to. * @param profile - The site profile containing navigation preferences. */ export declare function navigateToPage(page: Page, url: string, profile: ResolvedSiteProfile): Promise; /** * Finds the appropriate context (frame or page) containing the video element. Some streaming sites embed their video player in an iframe, which creates a * separate document context. We need to find this iframe and operate within it to access the video element. * * The search process: * 1. If the profile doesn't need iframe handling, return the main page directly * 2. Wait for an iframe element to appear in the DOM * 3. Allow time for the iframe content to initialize (embedded players often load additional resources) * 4. Search through all frames to find one containing a video element * 5. Fall back to the main page if no iframe contains a video * @param page - The Puppeteer page object. * @param profile - The site profile indicating whether iframe handling is needed. * @returns The frame or page containing the video element. */ export declare function findVideoContext(page: Page, profile: ResolvedSiteProfile): Promise; /** * Waits for the video element to reach a ready state indicating it has loaded enough data to begin playback. We use readyState >= 3 (HAVE_FUTURE_DATA) as the * threshold because: * * - readyState 0 (HAVE_NOTHING): No data available * - readyState 1 (HAVE_METADATA): Duration and dimensions known, but no media data * - readyState 2 (HAVE_CURRENT_DATA): Data for current position available, but not enough for playback * - readyState 3 (HAVE_FUTURE_DATA): Enough data for current position plus at least a little ahead * - readyState 4 (HAVE_ENOUGH_DATA): Enough data to play through without buffering (for known-length media) * * Live streams continuously receive data and may never reach readyState 4, so we use >= 3 as the threshold. The health monitor handles any subsequent buffering * or playback issues. * @param context - The frame or page containing the video element. * @param profile - The site profile with video selection preferences. */ export declare function waitForVideoReady(context: Frame | Page, profile: ResolvedSiteProfile): Promise; /** * Applies fullscreen styling to the video element using CSS to maximize the capture area. This CSS-based approach works for all sites regardless of their native * fullscreen mechanism (keyboard shortcuts, JavaScript API, etc.). * * The styling: * - position: fixed - Removes the video from document flow and positions relative to viewport * - top: 0; left: 0; width: 100%; height: 100% - Fills the entire viewport * - zIndex: 999000 - Ensures the video appears above all other page content * - objectFit: contain - Maintains aspect ratio while fitting within the viewport * - background: black - Fills any letterbox/pillarbox areas with black * - cursor: none - Hides the mouse cursor for cleaner capture * @param context - The frame or page containing the video element. * @param selectorType - The video selector type for finding the element. * @param important - When true, applies styles with !important priority to override site JavaScript that actively fights style changes. */ export declare function applyVideoStyles(context: Frame | Page, selectorType: VideoSelectorType, important?: boolean): Promise; /** * Locks the volume properties on the video element to prevent the site's JavaScript from muting our stream. Some sites (like France24) aggressively mute videos * or lower volume in response to various events. They may reset volume on play, on focus, on visibility change, or on a timer. * * This function uses Object.defineProperty to intercept property access, making it impossible for site JavaScript to change muted or volume values. The property * descriptors are set to configurable: true so the browser can still access the underlying values for playback. * * The function is idempotent - a __volumeLocked flag on the video element prevents applying the lock multiple times. * @param context - The frame or page containing the video element. * @param selectorType - The video selector type for finding the element. */ export declare function lockVolumeProperties(context: Frame | Page, selectorType: VideoSelectorType): Promise; /** * Triggers fullscreen mode using the appropriate method for the site. Different sites have different fullscreen implementations: * * - Keyboard shortcuts (fullscreenKey): Many players use "f" as a keyboard shortcut for fullscreen. We send this keypress to activate the player's native * fullscreen mode. * * - JavaScript Fullscreen API (useRequestFullscreen): Some players require calling video.requestFullscreen() directly. This may trigger browser permission * prompts or be blocked by CSP, but works on many sites. * * Note that we also apply CSS-based fullscreen styling separately (in applyVideoStyles), which provides a reliable fallback when native fullscreen methods fail. * @param page - The Puppeteer page object for keyboard input. * @param context - The frame or page containing the video element. * @param profile - The site profile indicating fullscreen method. * @param selectorType - The video selector type for finding the element. */ export declare function triggerFullscreen(page: Page, context: Frame | Page, profile: ResolvedSiteProfile, selectorType: VideoSelectorType): Promise; /** * Verifies that the video element is filling the viewport, indicating that fullscreen styling was successfully applied. This function checks the video element's * bounding rectangle against the viewport dimensions to determine if the video appears fullscreen. * * The verification allows for some tolerance because: * - The video may have letterboxing/pillarboxing due to aspect ratio differences * - Some browsers report slightly smaller dimensions due to scrollbars or UI chrome * - CSS rounding may cause small discrepancies * * We require the video to fill at least 85% of the viewport in at least one dimension (the constraining dimension for aspect ratio) and at least 50% in the * other dimension to catch obviously broken cases. * @param context - The frame or page containing the video element. * @param selectorType - The video selector type for finding the element. * @returns True if the video appears to be fullscreen, false if it does not, or null if the check could not be performed (e.g. context destroyed). */ export declare function verifyFullscreen(context: Frame | Page, selectorType: VideoSelectorType): Promise>; /** * Ensures the video is displayed fullscreen with verification and retry logic. For profiles that use the native Fullscreen API, this serializes through a * promise-chain mutex so only one tab activates fullscreen at a time — Chrome requires the tab to be in the foreground for requestFullscreen() to succeed, and * concurrent tabs would steal foreground from each other. When skipNativeFullscreen is true (monitor recovery), the mutex is bypassed entirely. * @param page - The Puppeteer page object for keyboard input. * @param context - The frame or page containing the video element. * @param profile - The site profile indicating fullscreen method. * @param selectorType - The video selector type for finding the element. * @param skipNativeFullscreen - When true, skips Fullscreen API-specific actions (click-for-activation, native fullscreen verification, API retries). CSS styling * and keyboard shortcuts still run. Used during monitor recovery where user activation is unavailable and click-for-activation can interfere with playback. */ export declare function ensureFullscreen(page: Page, context: Frame | Page, profile: ResolvedSiteProfile, selectorType: VideoSelectorType, skipNativeFullscreen?: boolean): Promise; /** * Options for ensurePlayback() that control recovery behavior. */ interface EnsurePlaybackOptions { /** The escalation level (1-2). Level 1 is basic play/unmute recovery. Level 2 adds video source reload. Defaults to 1. */ recoveryLevel?: number; /** When true, skips native Fullscreen API actions (click-for-activation, API verification, API retries) during the fullscreen step. CSS styling and keyboard * shortcuts still run. Used by the monitor during recovery where user activation is unavailable and click-for-activation can toggle playback state. The * monitor's own lightweight fullscreen maintenance loop handles ongoing CSS reapplication independently. Defaults to false. */ skipNativeFullscreen?: boolean; } /** * Ensures the video is playing with proper audio settings. This is the core playback function that handles both initial setup and recovery from stalls. It is * designed to be idempotent - safe to call multiple times without adverse effects. * * Recovery escalation levels (higher levels include all lower-level actions): * * LEVEL 1 - Basic recovery (default): * - Set muted=false and volume=1 * - Call play() if video is paused * - Ensure fullscreen with CSS styling, keyboard shortcuts, and dimension verification. When skipNativeFullscreen is set, Fullscreen API-specific actions are * skipped because user activation is unavailable and click-for-activation can interfere with playback recovery. * - Lock volume properties if profile requires it * * LEVEL 2 - Reload video source: * - All level 1 actions, plus: * - Reset video.src to empty, call load() * - Restore original src, call load() again * - Wait for source to reinitialize * - This forces the player to completely reinitialize, fixing stuck players * * Level 3 (full page navigation) is handled by the playback monitor, not this function. * @param page - The Puppeteer page object. * @param context - The frame or page containing the video element. * @param profile - The site profile containing all behavior flags. * @param options - Optional recovery configuration. Omit for initial tune (full fullscreen behavior, level 1). */ export declare function ensurePlayback(page: Page, context: Frame | Page, profile: ResolvedSiteProfile, options?: EnsurePlaybackOptions): Promise; /** * Performs all post-navigation channel initialization: selects the channel, finds the video context, clicks to play if needed, waits for video readiness, and * ensures playback with fullscreen styling. This function is separated from navigateToPage() so that retryOperation() in setup.ts can wrap only navigation with a * timeout, while channel selection and video setup run with their own internal time budgets (click retry loops, videoTimeout, etc.) without being killed by the * navigation timeout. * * For guideGrid channel selection failures, the function attempts a single retry after dismissing any stale overlay that may be covering the guide grid. This * handles the case where a failed click attempt left an overlay open, causing subsequent locateOnNowCell calls to fail. * * @param page - The Puppeteer page object. * @param profile - The site profile containing all behavior flags. * @param skipChannelSelection - When true, skip the channel selection phase entirely. Used when navigating directly to a cached watch URL that already targets * the correct channel — only video detection, playback, and fullscreen setup are needed. * @returns The video context (frame or page) for subsequent monitoring, and a directTune flag when the channel was tuned via API interception. */ export declare function initializePlayback(page: Page, profile: ResolvedSiteProfile, skipChannelSelection?: boolean): Promise; /** * Tunes to a channel by navigating to the URL and initializing video playback. This is the single source of truth for channel initialization, used by both initial * stream setup and recovery. Having one authoritative function ensures consistent behavior and prevents code divergence between setup and recovery paths. * * The tuning process: * 0. Check cache: If a direct watch URL is cached, navigate to it and skip channel selection. On failure, invalidate and fall through. * 1. Navigate: Load the target URL using site-appropriate wait conditions * 2. Select channel: For multi-channel players, click the desired channel in the UI * 3. Find video: Locate the video element (which may be in an iframe) * 4. Click to play: For Brightcove-style players, click the video to start playback * 5. Wait for ready: Ensure the video has buffered enough data to play * 6. Ensure playback: Start playback, unmute, and apply fullscreen styling * * Note: Stream context for logging is automatically retrieved from AsyncLocalStorage. Callers should wrap their stream handling code in runWithStreamContext() to * ensure log messages include the stream ID prefix. * * @param page - The Puppeteer page object. * @param url - The URL to navigate to. * @param profile - The site profile containing all behavior flags. * @returns The video context (frame or page) for subsequent monitoring. */ export declare function tuneToChannel(page: Page, url: string, profile: ResolvedSiteProfile): Promise; export {};