import React , { useEffect , useState , useRef , useCallback } from 'react' ; import { OverlayState } from '../constants/OverlayState' ; interface MutableVideoState { isPlayAttemptInProgress : boolean ; pauseTimeout : number ; loadingStateTimeout : number ; videoTimeToRestore : number ; previousIsVideoActive : boolean ; previousShouldPlayVideo : boolean ; } param { React . RefObject < HTMLVideoElement > } videoRef param { bool } shouldPlayVideo param { number } playbackRangeStart param { number } playbackRangeEnd param { bool } loop param { bool } restartOnPaused param { bool } shouldWaitForOverlayTransitionBeforePausing param { bool } hasLoadingOverlay param { number } overlayTransitionDuration param { number } loadingStateTimeout returns { [ OverlayState , bool ] } export default function useManageVideoPlayback ( videoRef , shouldPlayVideo , playbackRangeStart , playbackRangeEnd , loop , restartOnPaused , shouldWaitForOverlayTransitionBeforePausing , hasLoadingOverlay , overlayTransitionDuration , loadingStateTimeout ) { const [ overlayState , setOverlayState ] = useState < OverlayState > ( OverlayState . paused ) ; const [ isVideoLoadingOrPlaying , setIsVideoLoadingOrPlaying ] = useState < boolean > ( false ) ; const isVideoActive = shouldPlayVideo || isVideoLoadingOrPlaying ; const mutableVideoState = useRef < MutableVideoState > ( null ) ; if ( mutableVideoState . current === null ) { mutableVideoState . current = { isPlayAttemptInProgress : false , pauseTimeout : null , loadingStateTimeout : null , videoTimeToRestore : playbackRangeStart || 0 , previousIsVideoActive : false , previousShouldPlayVideo : false , } ; } const clearVideoStateTimeouts = useCallback ( ( ) => { clearTimeout ( mutableVideoState . current . pauseTimeout ) ; clearTimeout ( mutableVideoState . current . loadingStateTimeout ) ; } , [ ] ) ; useEffect ( ( ) => ( ) => clearVideoStateTimeouts ( ) , [ clearVideoStateTimeouts ] ) ; const attemptToPlayVideo = useCallback ( ( ) => { mutableVideoState . current . isPlayAttemptInProgress = true ; const videoElement = videoRef . current ; videoElement . play ( ) . catch ( ( error ) => { if ( ! videoElement . muted && error . name === 'NotAllowedError' ) { console . warn ( 'HoverVideoPlayer: Playback with sound was blocked by the browser. Attempting to play again with the video muted; audio will be restored if the user clicks on the page.' ) ; videoElement . muted = true ; videoElement . play ( ) ; const onClickDocument = ( ) => { videoElement . muted = false ; document . removeEventListener ( 'click' , onClickDocument ) ; } ; document . addEventListener ( 'click' , onClickDocument ) ; } else { console . error ( `HoverVideoPlayer: ${ error . message }` ) ; } } ) ; } , [ videoRef ] ) ; const attemptToPauseVideo = useCallback ( ( ) => { const videoElement = videoRef . current ; if ( ! videoElement . paused && ! mutableVideoState . current . isPlayAttemptInProgress ) { videoElement . pause ( ) ; } } , [ videoRef ] ) ; useEffect ( ( ) => { const videoElement = videoRef . current ; const onPause = ( ) => setIsVideoLoadingOrPlaying ( false ) ; const onPlay = ( ) => setIsVideoLoadingOrPlaying ( true ) ; videoElement . addEventListener ( 'pause' , onPause ) ; videoElement . addEventListener ( 'play' , onPlay ) ; return ( ) => { videoElement . removeEventListener ( 'pause' , onPause ) ; videoElement . removeEventListener ( 'play' , onPlay ) ; } ; } , [ videoRef ] ) ; useEffect ( ( ) => { const videoElement = videoRef . current ; const onPlaying = ( ) => { clearVideoStateTimeouts ( ) ; mutableVideoState . current . isPlayAttemptInProgress = false ; if ( shouldPlayVideo ) { setOverlayState ( OverlayState . playing ) ; } else { videoElement . pause ( ) ; } } ; videoElement . addEventListener ( 'playing' , onPlaying ) ; return ( ) => { videoElement . removeEventListener ( 'playing' , onPlaying ) ; } ; } , [ clearVideoStateTimeouts , shouldPlayVideo , videoRef ] ) ; useEffect ( ( ) => { if ( mutableVideoState . current . previousIsVideoActive === isVideoActive ) { return ; } mutableVideoState . current . previousIsVideoActive = isVideoActive ; if ( ! isVideoActive ) { const videoElement = videoRef . current ; clearVideoStateTimeouts ( ) ; if ( restartOnPaused ) { const resetStartTime = playbackRangeStart || 0 ; videoElement . currentTime = resetStartTime ; } mutableVideoState . current . videoTimeToRestore = videoElement . currentTime ; } } , [ clearVideoStateTimeouts , isVideoActive , playbackRangeStart , restartOnPaused , videoRef , ] ) ; useEffect ( ( ) => { if ( playbackRangeStart === null && playbackRangeEnd === null ) return undefined ; const videoElement = videoRef . current ; const keepVideoTimeWithinPlaybackRange = ( ) => { const playbackRangeMaxTime = playbackRangeEnd || videoElement . duration ; const playbackRangeMinTime = playbackRangeStart || 0 ; if ( videoElement . currentTime >= playbackRangeMaxTime ) { if ( loop ) { videoElement . currentTime = playbackRangeMinTime ; if ( shouldPlayVideo && ( videoElement . paused || videoElement . ended ) ) { attemptToPlayVideo ( ) ; } } else { attemptToPauseVideo ( ) ; if ( videoElement . currentTime > playbackRangeMaxTime ) { videoElement . currentTime = playbackRangeMaxTime ; } } } else if ( videoElement . currentTime < playbackRangeMinTime ) { videoElement . currentTime = playbackRangeMinTime ; } } ; keepVideoTimeWithinPlaybackRange ( ) ; videoElement . addEventListener ( 'timeupdate' , keepVideoTimeWithinPlaybackRange ) ; return ( ) => videoElement . removeEventListener ( 'timeupdate' , keepVideoTimeWithinPlaybackRange ) ; } , [ attemptToPauseVideo , attemptToPlayVideo , loop , playbackRangeEnd , playbackRangeStart , shouldPlayVideo , videoRef , ] ) ; useEffect ( ( ) => { if ( mutableVideoState . current . previousShouldPlayVideo === shouldPlayVideo ) { return ; } mutableVideoState . current . previousShouldPlayVideo = shouldPlayVideo ; clearVideoStateTimeouts ( ) ; const videoElement = videoRef . current ; const isVideoStopped = videoElement . paused || videoElement . ended ; if ( shouldPlayVideo ) { const isVideoLoadedEnoughToPlay = videoElement . readyState >= 3 ; if ( ( isVideoStopped || ! isVideoLoadedEnoughToPlay ) && hasLoadingOverlay ) { mutableVideoState . current . loadingStateTimeout = window . setTimeout ( ( ) => { setOverlayState ( OverlayState . loading ) ; } , loadingStateTimeout ) ; } if ( isVideoStopped ) { videoElement . currentTime = mutableVideoState . current . videoTimeToRestore ; attemptToPlayVideo ( ) ; } else if ( isVideoLoadedEnoughToPlay ) { setOverlayState ( OverlayState . playing ) ; } } else { setOverlayState ( OverlayState . paused ) ; if ( shouldWaitForOverlayTransitionBeforePausing && overlayTransitionDuration ) { mutableVideoState . current . pauseTimeout = window . setTimeout ( attemptToPauseVideo , overlayTransitionDuration ) ; } else { attemptToPauseVideo ( ) ; } } } , [ attemptToPauseVideo , attemptToPlayVideo , clearVideoStateTimeouts , hasLoadingOverlay , shouldWaitForOverlayTransitionBeforePausing , loadingStateTimeout , overlayTransitionDuration , shouldPlayVideo , videoRef , ] ) ; return [ overlayState , isVideoActive ] ; }