'use client'; import type { Media } from '@videojs/core/dom'; import type { UnknownState, UnknownStore } from '@videojs/store'; import { useStore } from '@videojs/store/react'; import type { Dispatch, HTMLAttributes, ReactNode, SetStateAction } from 'react'; import { createContext, forwardRef, useContext, useEffect, useRef } from 'react'; import { useComposedRefs } from '../utils/use-composed-refs'; export interface PlayerContextValue { store: UnknownStore; media: Media | null; setMedia: Dispatch>; setContainer: Dispatch>; } const PlayerContext = createContext(null); const EMPTY_UNSUBSCRIBE = () => {}; const EMPTY_STORE = { state: {} as UnknownState, subscribe: () => EMPTY_UNSUBSCRIBE, } as Pick; export function PlayerContextProvider({ value, children, }: { value: PlayerContextValue; children: ReactNode; }): ReactNode { return {children}; } /** Access the full player context value. Throws if used outside a Player Provider. */ export function usePlayerContext(): PlayerContextValue { const ctx = useContext(PlayerContext); if (!ctx) throw new Error('usePlayerContext must be used within a Player Provider'); return ctx; } /** * Access the player store from within a Player Provider. * * @label Without Selector */ export function usePlayer(): UnknownStore; /** * Select a value from the player store. Re-renders when the selected value changes. * * @label With Selector * @param selector - Derives a value from the player store state. */ export function usePlayer(selector: (state: UnknownState) => R): R; export function usePlayer(selector?: (state: UnknownState) => R) { const { store } = usePlayerContext(); return useStore(store, selector as any); } /** * Access player state when available, but return `undefined` outside Provider. * * This is useful for components that can operate without player context * (e.g. they accept fully explicit props as a fallback). */ /** @label Without Selector */ export function useOptionalPlayer(): UnknownStore | undefined; /** @label With Selector */ export function useOptionalPlayer(selector: (state: UnknownState) => R): R | undefined; export function useOptionalPlayer(selector?: (state: UnknownState) => R) { const ctx = useContext(PlayerContext); const store = (ctx?.store ?? (EMPTY_STORE as unknown as UnknownStore)) as UnknownStore; const value = useStore(store, (ctx ? selector : undefined) as any); return ctx ? value : undefined; } /** Access the media element from within a Player Provider. */ export function useMedia(): Media | null { const { media } = usePlayerContext(); return media; } /** Access the media attach setter for connecting a media element to the player. */ export function useMediaAttach(): Dispatch> | undefined { const ctx = useContext(PlayerContext); return ctx?.setMedia; } /** Access the container attach setter for connecting a container element to the player. */ export function useContainerAttach(): Dispatch> | undefined { const ctx = useContext(PlayerContext); return ctx?.setContainer; } export interface ContainerProps extends HTMLAttributes { children?: ReactNode; } export const Container = forwardRef(function Container({ children, ...props }, ref) { const setContainer = useContainerAttach(); const internalRef = useRef(null); const composedRef = useComposedRefs(ref, internalRef); useEffect(() => { setContainer?.(internalRef.current); return () => setContainer?.(null); }, [setContainer]); return (
{children}
); }); export namespace Container { export type Props = ContainerProps; }