import { MutableRefObject, ReactNode, useCallback, useEffect, useRef, } from 'react'; import {PlayerContext} from '@common/player/player-context'; import {MediaItem} from '@common/player/media-item'; import {PlayerOutlet} from '@common/player/ui/player-outlet'; import { PlayerActions, usePlayerActions, } from '@common/player/hooks/use-player-actions'; import {usePlayerStore} from '@common/player/hooks/use-player-store'; import clsx from 'clsx'; import {PlayerPoster} from '@common/player/ui/controls/player-poster'; import {usePlayerClickHandler} from '@common/player/hooks/use-player-click-handler'; import {IconButton} from '@common/ui/buttons/icon-button'; import {MediaPlayIcon} from '@common/icons/media/media-play'; import {BufferingSpinner} from '@common/player/ui/controls/buffering-spinner'; import {guessPlayerProvider} from '@common/player/utils/guess-player-provider'; import {usePrevious} from '@common/utils/hooks/use-previous'; import {PlayerStoreOptions} from '@common/player/state/player-store-options'; import {VideoPlayerControls} from '@common/player/ui/video-player/video-player-controls'; interface Props { id: string; queue?: MediaItem[]; cuedMediaId?: string; autoPlay?: boolean; src?: string; listeners?: PlayerStoreOptions['listeners']; onDestroy?: PlayerStoreOptions['onDestroy']; onBeforePlayNext?: PlayerStoreOptions['onBeforePlayNext']; onBeforePlayPrevious?: PlayerStoreOptions['onBeforePlayPrevious']; apiRef?: MutableRefObject; rightActions?: ReactNode; } export function VideoPlayer({ id, queue, cuedMediaId, autoPlay, src, listeners, onBeforePlayPrevious, onBeforePlayNext, onDestroy, apiRef, rightActions, }: Props) { return ( ); } interface PlayerLayoutProps { apiRef?: MutableRefObject; rightActions?: ReactNode; } function PlayerLayout({apiRef, rightActions}: PlayerLayoutProps) { const leaveTimerRef = useRef(); const inactiveTimerRef = useRef(); const pointerIsOverControls = useRef(false); const actions = usePlayerActions(); const controlsVisible = usePlayerStore(s => s.controlsVisible); const {setControlsVisible, getState} = actions; const clickHandler = usePlayerClickHandler(); const clearTimers = () => { if (leaveTimerRef.current) { clearTimeout(leaveTimerRef.current); leaveTimerRef.current = null; } if (inactiveTimerRef.current) { clearTimeout(inactiveTimerRef.current); inactiveTimerRef.current = null; } }; const startInactiveTimer = useCallback(() => { if (getState().isPlaying) { inactiveTimerRef.current = window.setTimeout(() => { setControlsVisible(false); }, 3500); } }, [getState, setControlsVisible]); useEffect(() => { if (apiRef) { apiRef.current = actions; return actions.subscribe({ play: () => startInactiveTimer(), }); } }, [apiRef, actions, setControlsVisible, startInactiveTimer]); return (
{ setControlsVisible(true); clearTimers(); }} onPointerMove={() => { if (pointerIsOverControls.current) { return; } if (inactiveTimerRef.current) { setControlsVisible(true); } clearTimers(); startInactiveTimer(); }} onPointerLeave={() => { clearTimers(); if (!getState().isPlaying) { return; } leaveTimerRef.current = window.setTimeout(() => { setControlsVisible(false); }, 2500); }} > { pointerIsOverControls.current = true; setControlsVisible(true); clearTimers(); }} onPointerLeave={() => { pointerIsOverControls.current = false; }} />
); } function OverlayButtons() { const showPlayButton = usePlayerStore(s => !s.isPlaying && !s.isSeeking); return (
); } // required in order for "onPointerEnter" to fire consistently when player provider is iframe function Blocker() { return
; } function BottomGradient() { const controlsVisible = usePlayerStore(s => s.controlsVisible); return (
); } function mediaItemFromSrc(src: string): MediaItem { return { id: src, src, provider: guessPlayerProvider(src), }; } interface QueueOverriderProps { src?: string; queue?: MediaItem[]; } function QueueOverrider({src, queue}: QueueOverriderProps) { const {getState, overrideQueue} = usePlayerActions(); const queueKey = queue?.map(item => item.id).join('-') ?? ''; const previousKey = usePrevious(queueKey); // override queue when any of specified queue item id or order changes useEffect(() => { if (queue && previousKey && queueKey && previousKey !== queueKey) { overrideQueue(queue); } }, [queueKey, previousKey, queue, overrideQueue]); // override queue when src changes useEffect(() => { if (src && getState().cuedMedia?.src !== src) { overrideQueue([mediaItemFromSrc(src)]); } }, [src, getState, overrideQueue]); return null; }