import { useSyncExternalStore, useRef, useCallback } from 'react'; import { useAviationPlayer } from '../AviationContext'; import { subscribePlaybackChannels, playbackFlags } from './playbackSnapshot'; import type { AviationError } from '../errors'; import type { PlaybackState, ItemSource } from '../specs/types.nitro'; import type { MediaItem } from '../specs/MediaItem.nitro'; import type { MediaItemConfig } from '../specs/types.nitro'; import type { AviationPlayer } from '../Aviation'; /** * Scoped playback state for a specific media item. * * All fields mirror {@link PlaybackInfo} but `isOwner` indicates whether * the given item is currently loaded on the engine. When `isOwner` is false, * all state fields reflect idle/false — the engine may be active with * different content but that state is not exposed here. */ export interface ScopedPlaybackInfo { /** Whether this item is the one currently loaded on the engine. */ isOwner: boolean; /** Current playback state. `'idle'` when this item is not the active one. */ state: PlaybackState; /** Whether this item is currently playing. */ isPlaying: boolean; /** Whether this item is paused. */ isPaused: boolean; /** Whether this item is loaded and ready. */ isLoaded: boolean; /** Whether this item is buffering. */ isBuffering: boolean; /** Whether the current media is a live stream. */ isLive: boolean; /** How this item was loaded (queue, direct, external, none). */ currentItemSource: ItemSource; /** Last playback error for this item, if state is 'error'. */ error: AviationError | undefined; } const IDLE_SCOPED: ScopedPlaybackInfo = { isOwner: false, state: 'idle', isPlaying: false, isPaused: false, isLoaded: false, isBuffering: false, isLive: false, currentItemSource: 'none', error: undefined, }; /** * Item identifier accepted by {@link usePlaybackFor}. * * Either a {@link MediaItem} HybridObject, a {@link MediaItemConfig} plain * object, or anything carrying a `uri` string. Ownership is decided by URI * (with a reference-equality fast path), so callers don't need to keep a * stable HybridObject reference across renders for this hook to work. */ export type PlaybackItemRef = MediaItem | MediaItemConfig | { uri: string }; /** * Scoped variant of {@link usePlayback} that only reflects player state when * the given item is the currently active one. * * @param item - The media item to scope to. Accepts a `MediaItem` * HybridObject, a `MediaItemConfig`, or any object with a `uri` field. * Pass `undefined` to get idle state. * * @example * ```tsx * function AudioPlayerControls({ track }: { track: MediaItemConfig }) { * const { isOwner, isPlaying } = usePlaybackFor(track); * * // isPlaying is only true when THIS track is the engine's current * // content — starting a video won't flip this to true. * return