import type { HybridObject } from 'react-native-nitro-modules'; import type { AdsConfig, AdBreakInfo, AdInfo } from './types.nitro'; /** * AdsController — Abstract ads controller interface. * * This is the generic contract that any ads adapter (Google IMA, FreeWheel, * Amazon TAM, etc.) must implement. It is defined in the core library so * that: * - An AviationPlayer can hold an AdsController reference * - The useAdState hook can subscribe to ad events * - App code can interact with ads without knowing the specific SDK * * Implementations live in separate packages (e.g., @aviation/ima-ads). * * Architecture: * The AdsController operates as a sidecar to the PlaybackCoordinator. * When an ad break triggers, it pauses content via the coordinator, * manages ad playback independently, then resumes content when done. * No state machine changes are required. * * Engine-binding lifecycle (intentional divergence): * Adapters that talk to native playback need to bind to a specific engine * before use. This binding is deliberately NOT part of this generic * contract: the mechanism is adapter-specific (the concrete IMA controller * exposes `bindToEngine(engineId)` on its own `IMAdsController` spec — see * packages/ima-ads/src/specs/IMAdsControllerFactory.nitro.ts), and core has * no generic code path that would call it. Core only ever holds an * `AdsController` reference for event subscription and manual control; the * adapter's `enable*()` entry point performs the bind using * `player.engine.engineId`. A future adapter that needs engine binding * should follow the same pattern (bind in its own enable path against its * own concrete spec) rather than expecting core to drive it. */ export interface AdsController extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> { // ─── Configuration ────────────────────────────────────────────── /** Apply ads configuration. Must be called before first loadAndPlay. */ configure(config: AdsConfig): void; /** * Set or update the VAST/VMAP ad tag URL. * * This is a configuration-only call — it stores the URL for use in the * next `requestAd()` call. It does NOT trigger an ad fetch or network * request. No ad error events will fire as a result of calling this method. * * Must be called before `requestAd()` — calling `requestAd(adTagUrl)` * without a prior `setAdTagUrl(adTagUrl)` results in a silent no-op * (IMA has not been given a tag to request against). */ setAdTagUrl(url: string): void; // ─── Programmatic Cue Points ──────────────────────────────────── /** Add a mid-roll cue point at the given content position. */ addCuePoint(timeMs: number, adTagUrl: string): void; /** Remove a previously added cue point. */ removeCuePoint(timeMs: number): void; /** Remove all programmatic cue points. */ clearCuePoints(): void; // ─── Manual Control ───────────────────────────────────────────── /** * Manually request an ad from the given tag URL. * Returns a promise that resolves when the ad break completes or fails. * * **Required call sequence for pre-roll ads:** * ```ts * controller.setAdTagUrl(adTagUrl); // 1. configure tag * await actions.load(mediaConfig); // 2. load content (idle → ready) * await controller.requestAd(adTagUrl); // 3. fetch + play ad * actions.play(); // 4. play content * ``` * Any other ordering produces broken behavior (ads loop, no ads, content * flashes before ad). Use `playWithPreroll()` from `@react-native-aviation/ima-ads` * to encapsulate this sequence automatically. * * @param adTagUrl - VAST tag URL to request. Must match the URL previously * passed to `setAdTagUrl()` — calling `requestAd` without a prior * `setAdTagUrl` results in a silent no-op. */ requestAd(adTagUrl: string): Promise; /** Skip the currently playing ad (if skippable). */ skipAd(): void; /** Pause the currently playing ad. */ pauseAd(): void; /** Resume a paused ad. */ resumeAd(): void; // ─── State Queries ────────────────────────────────────────────── /** Whether an ad is currently playing. */ readonly isPlayingAd: boolean; /** Info about the current ad break, or undefined if not in a break. */ readonly currentAdBreakInfo: AdBreakInfo | undefined; /** Info about the current individual ad, or undefined if no ad playing. */ readonly currentAdInfo: AdInfo | undefined; // ─── Events ───────────────────────────────────────────────────── /** An ad break has started. */ onAdBreakStarted(callback: (info: AdBreakInfo) => void): void; /** An ad break has ended. */ onAdBreakEnded(callback: (info: AdBreakInfo) => void): void; /** A single ad within a break has started playing. */ onAdStarted(callback: (info: AdInfo) => void): void; /** A single ad within a break has finished playing. */ onAdCompleted(callback: (info: AdInfo) => void): void; /** Ad playback progress update. */ onAdProgress(callback: (info: AdInfo) => void): void; /** User skipped an ad. */ onAdSkipped(callback: (info: AdInfo) => void): void; /** User tapped/clicked on an ad. */ onAdTapped(callback: (info: AdInfo) => void): void; /** An ad error occurred. */ onAdError(callback: (error: string) => void): void; /** All ad breaks have completed for the current content session. */ onAllAdsCompleted(callback: () => void): void; // ─── Lifecycle ────────────────────────────────────────────────── /** Release all ad resources. */ release(): void; }