qapp-core/src/components/VideoPlayer/useVideoPlayerController.tsx
2025-06-14 03:46:05 +03:00

307 lines
8.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 (01)
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
};
};