mirror of
https://github.com/Qortal/qapp-core.git
synced 2025-06-14 17:41:20 +00:00
started on controls
This commit is contained in:
parent
58d56e25dd
commit
1ba3bfca26
22
src/components/VideoPlayer/CustomFontTooltip.tsx
Normal file
22
src/components/VideoPlayer/CustomFontTooltip.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import { Box, Tooltip, TooltipProps } from "@mui/material";
|
||||
import { PropsWithChildren } from "react";
|
||||
|
||||
export interface CustomFontTooltipProps extends TooltipProps {
|
||||
fontSize?: string;
|
||||
}
|
||||
export const CustomFontTooltip = ({
|
||||
fontSize,
|
||||
title,
|
||||
children,
|
||||
...props
|
||||
}: PropsWithChildren<CustomFontTooltipProps>) => {
|
||||
if (!fontSize) fontSize = "160%";
|
||||
const text = <Box sx={{ fontSize: fontSize }}>{title}</Box>;
|
||||
|
||||
// put controls into individual components
|
||||
return (
|
||||
<Tooltip title={text} {...props} sx={{ display: "contents", ...props.sx }}>
|
||||
<div>{children}</div>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
116
src/components/VideoPlayer/LoadingVideo.tsx
Normal file
116
src/components/VideoPlayer/LoadingVideo.tsx
Normal file
@ -0,0 +1,116 @@
|
||||
import { Box, CircularProgress, Typography } from "@mui/material";
|
||||
|
||||
import { PlayArrow } from "@mui/icons-material";
|
||||
import { Status } from "../../state/publishes";
|
||||
|
||||
|
||||
interface LoadingVideoProps {
|
||||
status: Status | null
|
||||
percentLoaded: number
|
||||
isReady: boolean
|
||||
isLoading: boolean
|
||||
togglePlay: ()=> void
|
||||
}
|
||||
export const LoadingVideo = ({
|
||||
status, percentLoaded, isReady, isLoading, togglePlay
|
||||
}: LoadingVideoProps) => {
|
||||
|
||||
const getDownloadProgress = (percentLoaded: number) => {
|
||||
const progress = percentLoaded;
|
||||
return Number.isNaN(progress) ? "" : progress.toFixed(0) + "%";
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isLoading && status !== 'INITIAL' && (
|
||||
<Box
|
||||
position="absolute"
|
||||
top={0}
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={status === "READY" ? "55px " : 0}
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
zIndex={25}
|
||||
bgcolor="rgba(0, 0, 0, 0.6)"
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "10px",
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<CircularProgress color="secondary" />
|
||||
{status && (
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
component="div"
|
||||
sx={{
|
||||
color: "white",
|
||||
fontSize: "15px",
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
{status === "NOT_PUBLISHED" && (
|
||||
<>Video file was not published. Please inform the publisher!</>
|
||||
)}
|
||||
{status === "REFETCHING" ? (
|
||||
<>
|
||||
<>
|
||||
{getDownloadProgress(
|
||||
percentLoaded
|
||||
)}
|
||||
</>
|
||||
|
||||
<> Refetching in 25 seconds</>
|
||||
</>
|
||||
) : status === "DOWNLOADED" ? (
|
||||
<>Download Completed: building video...</>
|
||||
) : status !== "READY" ? (
|
||||
<>
|
||||
{getDownloadProgress(
|
||||
percentLoaded
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>Fetching video...</>
|
||||
)}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{(status === 'INITIAL') && (
|
||||
<>
|
||||
<Box
|
||||
position="absolute"
|
||||
top={0}
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
zIndex={500}
|
||||
bgcolor="rgba(0, 0, 0, 0.6)"
|
||||
onClick={() => {
|
||||
togglePlay();
|
||||
}}
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
<PlayArrow
|
||||
sx={{
|
||||
width: "50px",
|
||||
height: "50px",
|
||||
color: "white",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
69
src/components/VideoPlayer/MobileControlsBar.tsx
Normal file
69
src/components/VideoPlayer/MobileControlsBar.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import { MoreVert as MoreIcon } from "@mui/icons-material";
|
||||
import { Box, IconButton, Menu, MenuItem } from "@mui/material";
|
||||
import {
|
||||
FullscreenButton,
|
||||
ObjectFitButton,
|
||||
PictureInPictureButton,
|
||||
PlaybackRate,
|
||||
PlayButton,
|
||||
ProgressSlider,
|
||||
ReloadButton,
|
||||
VideoTime,
|
||||
VolumeControl,
|
||||
} from "./VideoControls";
|
||||
|
||||
export const MobileControlsBar = ({handleMenuOpen, handleMenuClose, anchorEl, controlsHeight}) => {
|
||||
|
||||
|
||||
const controlGroupSX = {
|
||||
display: "flex",
|
||||
gap: "5px",
|
||||
alignItems: "center",
|
||||
height: controlsHeight,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={controlGroupSX}>
|
||||
<PlayButton />
|
||||
<ReloadButton />
|
||||
<ProgressSlider />
|
||||
<VideoTime />
|
||||
</Box>
|
||||
|
||||
<Box sx={controlGroupSX}>
|
||||
<PlaybackRate />
|
||||
<FullscreenButton />
|
||||
|
||||
<IconButton
|
||||
edge="end"
|
||||
color="inherit"
|
||||
aria-label="menu"
|
||||
onClick={handleMenuOpen}
|
||||
sx={{ paddingLeft: "0px", marginRight: "0px" }}
|
||||
>
|
||||
<MoreIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Menu
|
||||
id="simple-menu"
|
||||
anchorEl={anchorEl.value}
|
||||
keepMounted
|
||||
open={Boolean(anchorEl.value)}
|
||||
onClose={handleMenuClose}
|
||||
PaperProps={{
|
||||
style: {
|
||||
width: "250px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<MenuItem>
|
||||
<ObjectFitButton />
|
||||
</MenuItem>
|
||||
<MenuItem>
|
||||
<PictureInPictureButton />
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</>
|
||||
);
|
||||
};
|
249
src/components/VideoPlayer/VideoControls.tsx
Normal file
249
src/components/VideoPlayer/VideoControls.tsx
Normal file
@ -0,0 +1,249 @@
|
||||
import { Box, IconButton, Slider, Typography } from "@mui/material";
|
||||
export const fontSizeExSmall = "60%";
|
||||
export const fontSizeSmall = "80%";
|
||||
import AspectRatioIcon from "@mui/icons-material/AspectRatio";
|
||||
import {
|
||||
Fullscreen,
|
||||
Pause,
|
||||
PictureInPicture,
|
||||
PlayArrow,
|
||||
Refresh,
|
||||
VolumeOff,
|
||||
VolumeUp,
|
||||
} from "@mui/icons-material";
|
||||
import { formatTime } from "../../utils/time.js";
|
||||
import { CustomFontTooltip } from "./CustomFontTooltip.js";
|
||||
|
||||
const buttonPaddingBig = "6px";
|
||||
const buttonPaddingSmall = "4px";
|
||||
|
||||
export const PlayButton = ({togglePlay, isPlaying , isScreenSmall}: any) => {
|
||||
return (
|
||||
<CustomFontTooltip title="Pause/Play (Spacebar)" placement="bottom" arrow>
|
||||
<IconButton
|
||||
sx={{
|
||||
color: "white",
|
||||
padding: isScreenSmall ? buttonPaddingSmall : buttonPaddingBig,
|
||||
}}
|
||||
onClick={() => togglePlay()}
|
||||
>
|
||||
{isPlaying ? <Pause /> : <PlayArrow />}
|
||||
</IconButton>
|
||||
</CustomFontTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export const ReloadButton = ({reloadVideo, isScreenSmall}: any) => {
|
||||
return (
|
||||
<CustomFontTooltip title="Reload Video (R)" placement="bottom" arrow>
|
||||
<IconButton
|
||||
sx={{
|
||||
color: "white",
|
||||
padding: isScreenSmall ? buttonPaddingSmall : buttonPaddingBig,
|
||||
}}
|
||||
onClick={reloadVideo}
|
||||
>
|
||||
<Refresh />
|
||||
</IconButton>
|
||||
</CustomFontTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export const ProgressSlider = ({progress, duration, videoRef}: any) => {
|
||||
const onProgressChange = async (_: any, value: number | number[]) => {
|
||||
if (!videoRef.current) return;
|
||||
videoRef.current.currentTime = value as number;
|
||||
};
|
||||
return (
|
||||
<Slider
|
||||
value={progress}
|
||||
onChange={onProgressChange}
|
||||
min={0}
|
||||
max={duration || 100}
|
||||
step={0.1}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
bottom: "42px",
|
||||
color: "#00abff",
|
||||
padding: "0px",
|
||||
// prevents the slider from jumping up 20px in certain mobile conditions
|
||||
"@media (pointer: coarse)": { padding: "0px" },
|
||||
|
||||
"& .MuiSlider-thumb": {
|
||||
backgroundColor: "#fff",
|
||||
width: "16px",
|
||||
height: "16px",
|
||||
},
|
||||
"& .MuiSlider-thumb::after": { width: "20px", height: "20px" },
|
||||
"& .MuiSlider-rail": { opacity: 0.5, height: "6px" },
|
||||
"& .MuiSlider-track": { height: "6px", border: "0px" },
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const VideoTime = ({videoRef, progress, isScreenSmall}: any) => {
|
||||
|
||||
return (
|
||||
<CustomFontTooltip
|
||||
title="Seek video in 10% increments (0-9)"
|
||||
placement="bottom"
|
||||
arrow
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: isScreenSmall ? fontSizeExSmall : fontSizeSmall,
|
||||
color: "white",
|
||||
visibility: !videoRef.current?.duration ? "hidden" : "visible",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
{videoRef.current?.duration ? formatTime(progress) : ""}
|
||||
{" / "}
|
||||
{videoRef.current?.duration
|
||||
? formatTime(videoRef.current?.duration)
|
||||
: ""}
|
||||
</Typography>
|
||||
</CustomFontTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const VolumeButton = ({isMuted, toggleMute}: any) => {
|
||||
return (
|
||||
<CustomFontTooltip
|
||||
title="Toggle Mute (M), Raise (UP), Lower (DOWN)"
|
||||
placement="bottom"
|
||||
arrow
|
||||
>
|
||||
<IconButton
|
||||
sx={{
|
||||
color: "white",
|
||||
}}
|
||||
onClick={toggleMute}
|
||||
>
|
||||
{isMuted ? <VolumeOff /> : <VolumeUp />}
|
||||
</IconButton>
|
||||
</CustomFontTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const VolumeSlider = ({ width, volume, onVolumeChange }: any) => {
|
||||
let color = "";
|
||||
if (volume <= 0.5) color = "green";
|
||||
else if (volume <= 0.75) color = "yellow";
|
||||
else color = "red";
|
||||
|
||||
return (
|
||||
<Slider
|
||||
value={volume}
|
||||
onChange={onVolumeChange}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
sx={{
|
||||
width,
|
||||
marginRight: "10px",
|
||||
color,
|
||||
"& .MuiSlider-thumb": {
|
||||
backgroundColor: "#fff",
|
||||
width: "16px",
|
||||
height: "16px",
|
||||
},
|
||||
"& .MuiSlider-thumb::after": { width: "16px", height: "16px" },
|
||||
"& .MuiSlider-rail": { opacity: 0.5, height: "6px" },
|
||||
"& .MuiSlider-track": { height: "6px", border: "0px" },
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const VolumeControl = ({ sliderWidth, onVolumeChange, volume }: any) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{ display: "flex", gap: "5px", alignItems: "center", width: "100%" }}
|
||||
>
|
||||
<VolumeButton />
|
||||
<VolumeSlider width={sliderWidth} onVolumeChange={onVolumeChange} volume={volume} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const PlaybackRate = ({playbackRate, increaseSpeed, isScreenSmall}: any) => {
|
||||
return (
|
||||
<CustomFontTooltip
|
||||
title="Video Speed. Increase (+ or >), Decrease (- or <)"
|
||||
placement="bottom"
|
||||
arrow
|
||||
>
|
||||
<IconButton
|
||||
sx={{
|
||||
color: "white",
|
||||
fontSize: fontSizeSmall,
|
||||
padding: isScreenSmall ? buttonPaddingSmall : buttonPaddingBig,
|
||||
}}
|
||||
onClick={() => increaseSpeed()}
|
||||
>
|
||||
{playbackRate}x
|
||||
</IconButton>
|
||||
</CustomFontTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export const ObjectFitButton = ({toggleObjectFit, isScreenSmall}: any) => {
|
||||
return (
|
||||
<CustomFontTooltip title="Toggle Aspect Ratio (O)" placement="bottom" arrow>
|
||||
<IconButton
|
||||
sx={{
|
||||
color: "white",
|
||||
padding: isScreenSmall ? buttonPaddingSmall : buttonPaddingBig,
|
||||
}}
|
||||
onClick={() => toggleObjectFit()}
|
||||
>
|
||||
<AspectRatioIcon />
|
||||
</IconButton>
|
||||
</CustomFontTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export const PictureInPictureButton = ({isFullscreen, toggleRef, togglePictureInPicture, isScreenSmall}: any) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{!isFullscreen && (
|
||||
<CustomFontTooltip
|
||||
title="Picture in Picture (P)"
|
||||
placement="bottom"
|
||||
arrow
|
||||
>
|
||||
<IconButton
|
||||
sx={{
|
||||
color: "white",
|
||||
padding: isScreenSmall ? buttonPaddingSmall : buttonPaddingBig,
|
||||
}}
|
||||
ref={toggleRef}
|
||||
onClick={togglePictureInPicture}
|
||||
>
|
||||
<PictureInPicture />
|
||||
</IconButton>
|
||||
</CustomFontTooltip>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const FullscreenButton = ({toggleFullscreen, isScreenSmall}: any) => {
|
||||
|
||||
return (
|
||||
<CustomFontTooltip title="Toggle Fullscreen (F)" placement="bottom" arrow>
|
||||
<IconButton
|
||||
sx={{
|
||||
color: "white",
|
||||
padding: isScreenSmall ? buttonPaddingSmall : buttonPaddingBig,
|
||||
}}
|
||||
onClick={() => toggleFullscreen()}
|
||||
>
|
||||
<Fullscreen />
|
||||
</IconButton>
|
||||
</CustomFontTooltip>
|
||||
);
|
||||
};
|
74
src/components/VideoPlayer/VideoControlsBar.tsx
Normal file
74
src/components/VideoPlayer/VideoControlsBar.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
import { Box } from "@mui/material";
|
||||
import { ControlsContainer } from "./VideoPlayer-styles";
|
||||
// import { MobileControlsBar } from "./MobileControlsBar";
|
||||
import {
|
||||
FullscreenButton,
|
||||
ObjectFitButton,
|
||||
PictureInPictureButton,
|
||||
PlaybackRate,
|
||||
PlayButton,
|
||||
ProgressSlider,
|
||||
ReloadButton,
|
||||
VideoTime,
|
||||
VolumeControl,
|
||||
} from "./VideoControls";
|
||||
import { Ref } from "react";
|
||||
|
||||
interface VideoControlsBarProps {
|
||||
canPlay: boolean
|
||||
isScreenSmall: boolean
|
||||
controlsHeight?: string
|
||||
videoRef:Ref<HTMLVideoElement>;
|
||||
progress: number;
|
||||
duration: number
|
||||
isPlaying: boolean;
|
||||
togglePlay: ()=> void;
|
||||
reloadVideo: ()=> void;
|
||||
volume: number
|
||||
onVolumeChange: (_: any, val: number)=> void
|
||||
}
|
||||
|
||||
export const VideoControlsBar = ({reloadVideo, onVolumeChange, volume, isPlaying, canPlay, isScreenSmall, controlsHeight, videoRef, duration, progress, togglePlay}: VideoControlsBarProps) => {
|
||||
|
||||
const showMobileControls = isScreenSmall && canPlay;
|
||||
|
||||
const controlGroupSX = {
|
||||
display: "flex",
|
||||
gap: "5px",
|
||||
alignItems: "center",
|
||||
height: controlsHeight,
|
||||
};
|
||||
|
||||
return (
|
||||
<ControlsContainer
|
||||
style={{
|
||||
padding: "0px",
|
||||
height: controlsHeight,
|
||||
}}
|
||||
>
|
||||
{showMobileControls ? (
|
||||
null
|
||||
// <MobileControlsBar />
|
||||
) : canPlay ? (
|
||||
<>
|
||||
<Box sx={controlGroupSX}>
|
||||
<PlayButton isPlaying={isPlaying} togglePlay={togglePlay}/>
|
||||
<ReloadButton reloadVideo={reloadVideo} />
|
||||
|
||||
<ProgressSlider videoRef={videoRef} progress={progress} duration={duration} />
|
||||
|
||||
<VolumeControl onVolumeChange={onVolumeChange} volume={volume} sliderWidth={"100px"} />
|
||||
<VideoTime videoRef={videoRef} progress={progress}/>
|
||||
</Box>
|
||||
|
||||
<Box sx={controlGroupSX}>
|
||||
<PlaybackRate />
|
||||
<ObjectFitButton />
|
||||
<PictureInPictureButton />
|
||||
<FullscreenButton />
|
||||
</Box>
|
||||
</>
|
||||
) : null}
|
||||
</ControlsContainer>
|
||||
);
|
||||
};
|
@ -1,16 +1,11 @@
|
||||
import {
|
||||
Ref,
|
||||
RefObject,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { Ref, RefObject, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { QortalGetMetadata } from "../../types/interfaces/resources";
|
||||
import { VideoContainer, VideoElement } from "./VideoPlayer-styles";
|
||||
import { useVideoPlayerHotKeys } from "./useVideoPlayerHotKeys";
|
||||
import { useProgressStore, useVideoStore } from "../../state/video";
|
||||
import { useVideoPlayerController } from "./useVideoPlayerController";
|
||||
import { LoadingVideo } from "./LoadingVideo";
|
||||
import { VideoControlsBar } from "./VideoControlsBar";
|
||||
|
||||
type StretchVideoType = "contain" | "fill" | "cover" | "none" | "scale-down";
|
||||
|
||||
@ -21,7 +16,7 @@ export interface VideoPlayerProps {
|
||||
showControls?: boolean;
|
||||
poster?: string;
|
||||
autoPlay?: boolean;
|
||||
onEnded?: (e: React.SyntheticEvent<HTMLVideoElement, Event>)=> void
|
||||
onEnded?: (e: React.SyntheticEvent<HTMLVideoElement, Event>) => void;
|
||||
}
|
||||
|
||||
const videoStyles = {
|
||||
@ -35,16 +30,22 @@ export const VideoPlayer = ({
|
||||
showControls,
|
||||
poster,
|
||||
autoPlay,
|
||||
onEnded
|
||||
onEnded,
|
||||
}: VideoPlayerProps) => {
|
||||
const containerRef = useRef<RefObject<HTMLDivElement> | null>(null);
|
||||
const [videoObjectFit] = useState<StretchVideoType>("contain");
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [volume, setVolume] = useState(0);
|
||||
const { volume, setVolume } = useVideoStore((state) => ({
|
||||
volume: state.playbackSettings.volume,
|
||||
setVolume: state.setVolume,
|
||||
}));
|
||||
|
||||
const [isMuted, setIsMuted] = useState(false);
|
||||
const { setProgress } = useProgressStore();
|
||||
const [localProgress, setLocalProgress] = useState(0)
|
||||
const [duration, setDuration] = useState(0)
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const {
|
||||
reloadVideo,
|
||||
@ -67,7 +68,13 @@ export const VideoPlayer = ({
|
||||
startPlay,
|
||||
setProgressAbsolute,
|
||||
setAlwaysShowControls,
|
||||
} = useVideoPlayerController({ autoPlay, videoRef, qortalVideoResource, retryAttempts });
|
||||
status, percentLoaded
|
||||
} = useVideoPlayerController({
|
||||
autoPlay,
|
||||
videoRef,
|
||||
qortalVideoResource,
|
||||
retryAttempts
|
||||
});
|
||||
|
||||
const hotkeyHandlers = useMemo(
|
||||
() => ({
|
||||
@ -109,8 +116,18 @@ export const VideoPlayer = ({
|
||||
if (!ref.current || !videoLocation) return;
|
||||
if (typeof ref.current.currentTime === "number") {
|
||||
setProgress(videoLocation, ref.current.currentTime);
|
||||
setLocalProgress(ref.current.currentTime)
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
const ref = videoRef as React.RefObject<HTMLVideoElement>;
|
||||
if (!ref.current) return;
|
||||
if (ref.current) {
|
||||
ref.current.volume = volume;
|
||||
}
|
||||
// Only run on mount
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const onPlay = useCallback(() => {
|
||||
setIsPlaying(true);
|
||||
@ -152,11 +169,37 @@ export const VideoPlayer = ({
|
||||
};
|
||||
}, [videoObjectFit, showControls, isFullscreen]);
|
||||
|
||||
const handleEnded = useCallback((e: React.SyntheticEvent<HTMLVideoElement, Event>)=> {
|
||||
if(onEnded){
|
||||
onEnded(e)
|
||||
}
|
||||
}, [onEnded])
|
||||
const handleEnded = useCallback(
|
||||
(e: React.SyntheticEvent<HTMLVideoElement, Event>) => {
|
||||
if (onEnded) {
|
||||
onEnded(e);
|
||||
}
|
||||
},
|
||||
[onEnded]
|
||||
);
|
||||
|
||||
const handleCanPlay = useCallback(()=> {
|
||||
setIsLoading(false);
|
||||
}, [setIsLoading])
|
||||
|
||||
useEffect(() => {
|
||||
const ref = videoRef as React.RefObject<HTMLVideoElement>;
|
||||
if (!ref.current) return;
|
||||
const video = ref.current;
|
||||
if (!video) return;
|
||||
|
||||
const handleLoadedMetadata = () => {
|
||||
if(video?.duration){
|
||||
setDuration(video.duration)
|
||||
}
|
||||
};
|
||||
|
||||
video.addEventListener('loadedmetadata', handleLoadedMetadata);
|
||||
|
||||
return () => {
|
||||
video.removeEventListener('loadedmetadata', handleLoadedMetadata);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<VideoContainer
|
||||
@ -166,7 +209,7 @@ export const VideoPlayer = ({
|
||||
onMouseLeave={handleMouseLeave}
|
||||
ref={containerRef}
|
||||
>
|
||||
{/* <LoadingVideo /> */}
|
||||
<LoadingVideo togglePlay={togglePlay} isReady={isReady} status={status} percentLoaded={percentLoaded} isLoading={isLoading} />
|
||||
<VideoElement
|
||||
id={qortalVideoResource?.identifier}
|
||||
ref={videoRef}
|
||||
@ -175,18 +218,16 @@ export const VideoPlayer = ({
|
||||
poster={startPlay ? "" : poster}
|
||||
onTimeUpdate={updateProgress}
|
||||
autoPlay={autoPlay}
|
||||
onClick={() => togglePlay()}
|
||||
onEnded={handleEnded}
|
||||
onCanPlay={() => {
|
||||
setIsLoading(false);
|
||||
}}
|
||||
onClick={togglePlay}
|
||||
onEnded={handleEnded}
|
||||
onCanPlay={handleCanPlay}
|
||||
preload="metadata"
|
||||
style={videoStylesVideo}
|
||||
onPlay={onPlay}
|
||||
onPause={onPause}
|
||||
onVolumeChange={onVolumeChangeHandler}
|
||||
/>
|
||||
{/* {showControls && <VideoControlsBar />} */}
|
||||
<VideoControlsBar onVolumeChange={onVolumeChange} volume={volume} togglePlay={togglePlay} reloadVideo={hotkeyHandlers.reloadVideo} isPlaying={isPlaying} canPlay={true} isScreenSmall={false} controlsHeight={controlsHeight} videoRef={videoRef} duration={duration} progress={localProgress} />
|
||||
</VideoContainer>
|
||||
);
|
||||
};
|
||||
|
@ -42,7 +42,7 @@ export const useVideoPlayerController = (props: UseVideoControls) => {
|
||||
const { playbackSettings, setPlaybackRate, setVolume } = useVideoStore();
|
||||
const { getProgress } = useProgressStore();
|
||||
|
||||
const { isReady, resourceUrl } = useResourceStatus({
|
||||
const { isReady, resourceUrl, status, percentLoaded } = useResourceStatus({
|
||||
resource: !startedFetch ? null : qortalVideoResource,
|
||||
retryAttempts,
|
||||
});
|
||||
@ -194,7 +194,7 @@ export const useVideoPlayerController = (props: UseVideoControls) => {
|
||||
ref.current.load();
|
||||
ref.current.currentTime = currentTime;
|
||||
ref.current.play();
|
||||
}, []);
|
||||
}, [isReady, resourceUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
if (autoPlay) togglePlay();
|
||||
@ -227,5 +227,6 @@ export const useVideoPlayerController = (props: UseVideoControls) => {
|
||||
isReady,
|
||||
resourceUrl,
|
||||
startPlay,
|
||||
status, percentLoaded
|
||||
};
|
||||
};
|
||||
|
@ -200,7 +200,7 @@ export const useResourceStatus = ({
|
||||
const resourceUrl = resource ? `/arbitrary/${resource.service}/${resource.name}/${resource.identifier}` : null;
|
||||
|
||||
return useMemo(() => ({
|
||||
status: status?.status || "SEARCHING",
|
||||
status: status?.status || "INITIAL",
|
||||
localChunkCount: status?.localChunkCount || 0,
|
||||
totalChunkCount: status?.totalChunkCount || 0,
|
||||
percentLoaded: status?.percentLoaded || 0,
|
||||
|
@ -21,6 +21,7 @@ export type Status =
|
||||
| 'FAILED_TO_DOWNLOAD'
|
||||
| 'REFETCHING'
|
||||
| 'SEARCHING'
|
||||
| 'INITIAL'
|
||||
|
||||
export interface ResourceStatus {
|
||||
status: Status
|
||||
|
@ -25,4 +25,29 @@ export function formatTimestamp(timestamp: number): string {
|
||||
export function oneMonthAgo(){
|
||||
const oneMonthAgoTimestamp = dayjs().subtract(1, "month").valueOf();
|
||||
return oneMonthAgoTimestamp
|
||||
}
|
||||
|
||||
export function formatTime(seconds: number): string {
|
||||
seconds = Math.floor(seconds);
|
||||
const minutes: number | string = Math.floor(seconds / 60);
|
||||
let hours: number | string = Math.floor(minutes / 60);
|
||||
|
||||
let remainingSeconds: number | string = seconds % 60;
|
||||
let remainingMinutes: number | string = minutes % 60;
|
||||
|
||||
if (remainingSeconds < 10) {
|
||||
remainingSeconds = "0" + remainingSeconds;
|
||||
}
|
||||
|
||||
if (remainingMinutes < 10) {
|
||||
remainingMinutes = "0" + remainingMinutes;
|
||||
}
|
||||
|
||||
if (hours === 0) {
|
||||
hours = "";
|
||||
} else {
|
||||
hours = hours + ":";
|
||||
}
|
||||
|
||||
return hours + remainingMinutes + ":" + remainingSeconds;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user