mirror of
https://github.com/Qortal/qapp-core.git
synced 2025-06-15 01:41:21 +00:00
307 lines
8.1 KiB
TypeScript
307 lines
8.1 KiB
TypeScript
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
|
||
};
|
||
};
|