import { useSyncExternalStore, useRef } from 'react'; import { useAviationPlayer } from '../AviationContext'; import type { AdBreakInfo, AdInfo } from '../specs/types.nitro'; import type { AviationPlayer } from '../Aviation'; /** * Snapshot of the current ad playback state. */ export interface AdStateInfo { /** Whether an ad is currently playing. */ isPlayingAd: boolean; /** Info about the current ad break, or undefined if not in a break. */ currentAdBreak: AdBreakInfo | undefined; /** Info about the current individual ad, or undefined. */ currentAd: AdInfo | undefined; } const IDLE_AD_STATE: AdStateInfo = { isPlayingAd: false, currentAdBreak: undefined, currentAd: undefined, }; /** * React hook that subscribes to the ads controller's state. * * Uses `useSyncExternalStore` for tear-free reads. Re-renders only when * ad state actually changes. * * Returns idle state if no ads controller is registered. * * @example * ```tsx * function AdOverlay() { * const { isPlayingAd, currentAd } = useAdState(); * if (!isPlayingAd || !currentAd) return null; * return Ad {currentAd.adIndexInBreak + 1}/{currentAd.totalAdsInBreak}; * } * ``` */ export function useAdState(playerArg?: AviationPlayer): AdStateInfo { const player = useAviationPlayer(playerArg); const store = player.store; const ref = useRef(IDLE_AD_STATE); const getSnapshot = () => { const isPlayingAd = store.isPlayingAd; const currentAdBreak = store.currentAdBreak; const currentAd = store.currentAd; const prev = ref.current; if ( prev.isPlayingAd === isPlayingAd && prev.currentAdBreak === currentAdBreak && prev.currentAd === currentAd ) { return prev; } ref.current = { isPlayingAd, currentAdBreak, currentAd, }; return ref.current; }; return useSyncExternalStore(store.subscribeAdState, getSnapshot); }