import { useSyncExternalStore, useRef, useCallback } from 'react'; import { useAviationPlayer } from '../AviationContext'; import { IDLE_POSITION, samePosition } from '../snapshots'; import type { MediaPosition } from '../specs/types.nitro'; import type { PlaybackItemRef } from './usePlaybackFor'; import type { AviationPlayer } from '../Aviation'; /** * Scoped variant of {@link useMediaPosition} that returns idle (zeroed) * position when the given item is not the engine's currently active * content. * * Gates position reads on URI ownership so a UI for one item does not * render another item's playhead. * * @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 position. * * @example * ```tsx * function AudioProgressBar({ track }: { track: MediaItemConfig }) { * const { progress, currentMs, durationMs } = useMediaPositionFor(track); * // Stays at 0 when video is playing; reflects audio progress * // only when this track is the active one. * return ; * } * ``` * * @remarks * Ownership matches when the passed item shares either JS reference * identity OR `uri` with `store.currentItem` — same logic as * {@link usePlaybackFor}. */ export function useMediaPositionFor( item: PlaybackItemRef | undefined, playerArg?: AviationPlayer ): MediaPosition { const player = useAviationPlayer(playerArg); const store = player.store; const ref = useRef(IDLE_POSITION); const subscribePosition = useCallback( (cb: () => void): (() => void) => { const unsubs = [ store.subscribePosition(cb), store.subscribeCurrentItem(cb), ]; return () => unsubs.forEach((u) => u()); }, [store] ); const getSnapshot = () => { const currentItem = store.currentItem; const itemUri = item !== undefined ? (item as { uri?: string }).uri : undefined; const isOwner = item !== undefined && currentItem !== undefined && (currentItem === (item as unknown as typeof currentItem) || (itemUri !== undefined && currentItem.uri === itemUri)); if (!isOwner) { // Not the active item — return stable zeroed position. if (ref.current === IDLE_POSITION) return ref.current; ref.current = IDLE_POSITION; return ref.current; } const pos = store.mediaPosition; const prev = ref.current; if (samePosition(prev, pos)) return prev; ref.current = pos; return ref.current; }; return useSyncExternalStore(subscribePosition, getSnapshot); }