import { useState, useEffect, RefObject, useMemo, useCallback, Ref, useRef, useImperativeHandle, } from "react"; import { useProgressStore, useVideoStore } from "../../state/video"; import { QortalGetMetadata } from "../../types/interfaces/resources"; import { useResourceStatus } from "../../hooks/useResourceStatus"; import useIdleTimeout from "../../common/useIdleTimeout"; const controlsHeight = "42px"; const minSpeed = 0.25; const maxSpeed = 4.0; const speedChange = 0.25; interface UseVideoControls { playerRef: any; autoPlay?: boolean; qortalVideoResource: QortalGetMetadata; retryAttempts?: number; isPlayerInitialized: boolean } export const useVideoPlayerController = (props: UseVideoControls) => { const { autoPlay, playerRef, qortalVideoResource, retryAttempts, isPlayerInitialized } = props; const [isFullscreen, setIsFullscreen] = useState(false); const [showControlsFullScreen, setShowControlsFullScreen] = useState(false) const [videoObjectFit, setVideoObjectFit] = useState<"contain" | "fill">( "contain" ); const [alwaysShowControls, setAlwaysShowControls] = useState(false); const [startPlay, setStartPlay] = useState(false); const [startedFetch, setStartedFetch] = useState(false); const startedFetchRef = useRef(false); const { playbackSettings, setPlaybackRate, setVolume } = useVideoStore(); const { getProgress } = useProgressStore(); const { isReady, resourceUrl, status, percentLoaded } = useResourceStatus({ resource: !startedFetch ? null : qortalVideoResource, retryAttempts, }); const idleTime = 5000; // Time in milliseconds useIdleTimeout({ onIdle: () => (setShowControlsFullScreen(false)), onActive: () => (setShowControlsFullScreen(true)), idleTime, }); const videoLocation = useMemo(() => { if (!qortalVideoResource) return null; return `${qortalVideoResource.service}-${qortalVideoResource.name}-${qortalVideoResource.identifier}`; }, [qortalVideoResource]); useEffect(() => { if (videoLocation && isPlayerInitialized) { console.log('hellohhhh5') try { const ref = playerRef as any; if (!ref.current) return; const savedProgress = getProgress(videoLocation); console.log('savedProgress', savedProgress) if (typeof savedProgress === "number") { playerRef.current?.currentTime(savedProgress); } } catch (error) { console.error('line 74', error) } } }, [videoLocation, getProgress, isPlayerInitialized]); const [playbackRate, _setLocalPlaybackRate] = useState( playbackSettings.playbackRate ); const updatePlaybackRate = useCallback( (newSpeed: number) => { try { const player = playerRef.current; if (!player) return; if (newSpeed > maxSpeed || newSpeed < minSpeed) newSpeed = minSpeed; const clampedSpeed = Math.min(Math.max(newSpeed, minSpeed), maxSpeed); player.playbackRate(clampedSpeed); // ✅ Video.js API // _setLocalPlaybackRate(clampedSpeed); // setPlaybackRate(clampedSpeed); } catch (error) { console.error('updatePlaybackRate', error) } }, [setPlaybackRate, _setLocalPlaybackRate, minSpeed, maxSpeed] ); const increaseSpeed = useCallback( (wrapOverflow = true) => { try { const changedSpeed = playbackSettings.playbackRate + speedChange; const newSpeed = wrapOverflow ? changedSpeed : Math.min(changedSpeed, maxSpeed); updatePlaybackRate(newSpeed); } catch (error) { console.error('increaseSpeed', increaseSpeed) } }, [updatePlaybackRate, playbackSettings.playbackRate] ); const decreaseSpeed = useCallback(() => { updatePlaybackRate(playbackSettings.playbackRate - speedChange); }, [updatePlaybackRate, playbackSettings.playbackRate]); const toggleAlwaysShowControls = useCallback(() => { setAlwaysShowControls((prev) => !prev); }, [setAlwaysShowControls]); useEffect(() => { const handleFullscreenChange = () => { setIsFullscreen(!!document.fullscreenElement); }; document.addEventListener("fullscreenchange", handleFullscreenChange); return () => document.removeEventListener("fullscreenchange", handleFullscreenChange); }, []); const onVolumeChange = useCallback( (_: any, value: number | number[]) => { try { const newVolume = value as number; const ref = playerRef as any; if (!ref.current) return; if (ref.current) { playerRef.current?.volume(newVolume); } } catch (error) { console.error('onVolumeChange', error) } }, [] ); const toggleMute = useCallback(() => { try { const ref = playerRef as any; if (!ref.current?.muted) return; ref.current?.muted(!ref.current?.muted) } catch (error) { console.error('toggleMute', toggleMute) } }, []); const changeVolume = useCallback( (delta: number) => { try { const player = playerRef.current; if (!player || typeof player.volume !== 'function') return; const currentVolume = player.volume(); // Get current volume (0–1) let newVolume = Math.max(0, Math.min(currentVolume + delta, 1)); newVolume = +newVolume.toFixed(2); // Round to 2 decimal places player.volume(newVolume); // Set new volume player.muted(false); // Ensure it's unmuted } catch (error) { console.error('changeVolume', error) } }, [] ); const setProgressRelative = useCallback((seconds: number) => { try { const player = playerRef.current; if (!player || typeof player.currentTime !== 'function' || typeof player.duration !== 'function') return; const current = player.currentTime(); const duration = player.duration() || 100; const newTime = Math.max(0, Math.min(current + seconds, duration)); player.currentTime(newTime); } catch (error) { console.error('setProgressRelative', error) } }, []); const setProgressAbsolute = useCallback((percent: number) => { try { const player = playerRef.current; if (!player || typeof player.duration !== 'function' || typeof player.currentTime !== 'function') return; const duration = player.duration(); const clampedPercent = Math.min(100, Math.max(0, percent)); const finalTime = (duration * clampedPercent) / 100; player.currentTime(finalTime); } catch (error) { console.error('setProgressAbsolute', error) } }, []); const toggleObjectFit = useCallback(() => { setVideoObjectFit(videoObjectFit === "contain" ? "fill" : "contain"); }, [setVideoObjectFit]); const togglePlay = useCallback(async () => { try { if (!startedFetchRef.current) { setStartedFetch(true); startedFetchRef.current = true; setStartPlay(true); return; } const player = playerRef.current; if (!player) return; if (isReady) { if (player.paused()) { try { await player.play(); } catch (err) { console.warn('Play failed:', err); } } else { player.pause(); } } } catch (error) { console.error('togglePlay', error) } }, [setStartedFetch, isReady]); const reloadVideo = useCallback(async () => { try { const player = playerRef.current; if (!player || !isReady || !resourceUrl) return; const currentTime = player.currentTime(); player.src({ src: resourceUrl, type: 'video/mp4' }); // Adjust type if needed player.load(); player.ready(() => { player.currentTime(currentTime); player.play().catch((err: any) => { console.warn('Playback failed after reload:', err); }); }); } catch (error) { console.error(error) } }, [isReady, resourceUrl]); useEffect(() => { if (autoPlay) togglePlay(); }, [autoPlay]); useEffect(() => { if (isReady) { togglePlay(); } }, [togglePlay, isReady]); return { reloadVideo, togglePlay, onVolumeChange, increaseSpeed, decreaseSpeed, toggleMute, isFullscreen, toggleObjectFit, controlsHeight, setProgressRelative, toggleAlwaysShowControls, changeVolume, setProgressAbsolute, setAlwaysShowControls, startedFetch, isReady, resourceUrl, startPlay, status, percentLoaded, showControlsFullScreen }; };