3
0
mirror of https://github.com/Qortal/q-tube.git synced 2025-02-11 17:55:51 +00:00

Merge pull request #55 from QortalSeth/main

Updates to VideoPlayer controls
This commit is contained in:
Qortal Dev 2024-12-20 10:22:57 -07:00 committed by GitHub
commit 84c07b07cd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 440 additions and 304 deletions

20
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "qtube", "name": "qtube",
"version": "2.0.0", "version": "2.1.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "qtube", "name": "qtube",
"version": "2.0.0", "version": "2.1.0",
"dependencies": { "dependencies": {
"@emotion/react": "^11.10.6", "@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6", "@emotion/styled": "^11.10.6",
@ -23,6 +23,7 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-dropzone": "^14.2.3", "react-dropzone": "^14.2.3",
"react-idle-timer": "^5.7.2",
"react-intersection-observer": "^9.4.3", "react-intersection-observer": "^9.4.3",
"react-quill": "^2.0.0", "react-quill": "^2.0.0",
"react-redux": "^8.0.5", "react-redux": "^8.0.5",
@ -3958,6 +3959,15 @@
"react": ">= 16.8 || 18.0.0" "react": ">= 16.8 || 18.0.0"
} }
}, },
"node_modules/react-idle-timer": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/react-idle-timer/-/react-idle-timer-5.7.2.tgz",
"integrity": "sha512-+BaPfc7XEUU5JFkwZCx6fO1bLVK+RBlFH+iY4X34urvIzZiZINP6v2orePx3E6pAztJGE7t4DzvL7if2SL/0GQ==",
"peerDependencies": {
"react": ">=16",
"react-dom": ">=16"
}
},
"node_modules/react-intersection-observer": { "node_modules/react-intersection-observer": {
"version": "9.5.0", "version": "9.5.0",
"resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.5.0.tgz", "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.5.0.tgz",
@ -7321,6 +7331,12 @@
"prop-types": "^15.8.1" "prop-types": "^15.8.1"
} }
}, },
"react-idle-timer": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/react-idle-timer/-/react-idle-timer-5.7.2.tgz",
"integrity": "sha512-+BaPfc7XEUU5JFkwZCx6fO1bLVK+RBlFH+iY4X34urvIzZiZINP6v2orePx3E6pAztJGE7t4DzvL7if2SL/0GQ==",
"requires": {}
},
"react-intersection-observer": { "react-intersection-observer": {
"version": "9.5.0", "version": "9.5.0",
"resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.5.0.tgz", "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.5.0.tgz",

View File

@ -25,6 +25,7 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-dropzone": "^14.2.3", "react-dropzone": "^14.2.3",
"react-idle-timer": "^5.7.2",
"react-intersection-observer": "^9.4.3", "react-intersection-observer": "^9.4.3",
"react-quill": "^2.0.0", "react-quill": "^2.0.0",
"react-redux": "^8.0.5", "react-redux": "^8.0.5",

View File

@ -1,109 +0,0 @@
import {
Fullscreen,
MoreVert as MoreIcon,
Pause,
PictureInPicture,
PlayArrow,
Refresh,
VolumeUp,
} from "@mui/icons-material";
import { IconButton, Menu, MenuItem, Slider, Typography } from "@mui/material";
import { useVideoContext } from "./VideoContext.ts";
export const MobileControls = () => {
const {
togglePlay,
reloadVideo,
onProgressChange,
videoRef,
handleMenuOpen,
handleMenuClose,
onVolumeChange,
increaseSpeed,
togglePictureInPicture,
toggleFullscreen,
playing,
progress,
anchorEl,
volume,
playbackRate,
} = useVideoContext();
return (
<>
<IconButton
sx={{
color: "rgba(255, 255, 255, 0.7)",
}}
onClick={() => togglePlay()}
>
{playing.value ? <Pause /> : <PlayArrow />}
</IconButton>
<IconButton
sx={{
color: "rgba(255, 255, 255, 0.7)",
}}
onClick={reloadVideo}
>
<Refresh />
</IconButton>
<Slider
value={progress.value}
onChange={onProgressChange}
min={0}
max={videoRef.current?.duration || 100}
sx={{ flexGrow: 1, mx: 2 }}
/>
<Typography
sx={{
color: "rgba(255, 255, 255, 0.7)",
fontSize: "14px",
userSelect: "none",
minWidth: "30px",
}}
onClick={() => increaseSpeed()}
>
{playbackRate}x
</Typography>
<Fullscreen onClick={toggleFullscreen} />
<IconButton
edge="end"
color="inherit"
aria-label="menu"
onClick={handleMenuOpen}
sx={{ minWidth: "30px" }}
>
<MoreIcon />
</IconButton>
<Menu
id="simple-menu"
anchorEl={anchorEl.value}
keepMounted
open={Boolean(anchorEl.value)}
onClose={handleMenuClose}
PaperProps={{
style: {
width: "250px",
},
}}
>
<MenuItem>
<VolumeUp />
<Slider
value={volume.value}
onChange={onVolumeChange}
min={0}
max={1}
step={0.01}
/>
</MenuItem>
<MenuItem onClick={togglePictureInPicture}>
<PictureInPicture />
</MenuItem>
</Menu>
</>
);
};

View File

@ -0,0 +1,66 @@
import { MoreVert as MoreIcon } from "@mui/icons-material";
import { Box, IconButton, Menu, MenuItem } from "@mui/material";
import { useVideoContext } from "./VideoContext.ts";
import {
FullscreenButton,
PictureInPictureButton,
PlaybackRate,
PlayButton,
ProgressSlider,
ReloadButton,
VideoTime,
VolumeButton,
VolumeSlider,
} from "./VideoControls.tsx";
export const MobileControlsBar = () => {
const { handleMenuOpen, handleMenuClose, anchorEl } = useVideoContext();
const controlGroupSX = { display: "flex", gap: "5px", alignItems: "center" };
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={{ minWidth: "30px", paddingLeft: "0px" }}
>
<MoreIcon />
</IconButton>
</Box>
<Menu
id="simple-menu"
anchorEl={anchorEl.value}
keepMounted
open={Boolean(anchorEl.value)}
onClose={handleMenuClose}
PaperProps={{
style: {
width: "250px",
},
}}
>
<MenuItem>
<VolumeButton />
<VolumeSlider />
</MenuItem>
<MenuItem>
<PictureInPictureButton />
</MenuItem>
</Menu>
</>
);
};

View File

@ -5,16 +5,8 @@ import { useEffect } from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { Key } from "ts-key-enum"; import { Key } from "ts-key-enum";
import { useIsMobile } from "../../../../hooks/useIsMobile.ts";
import { setVideoPlaying } from "../../../../state/features/globalSlice.ts"; import { setVideoPlaying } from "../../../../state/features/globalSlice.ts";
import { import { RootState } from "../../../../state/store.ts";
setIsMuted,
setMutedVolumeSetting,
setReduxPlaybackRate,
setStretchVideoSetting,
setVolumeSetting,
} from "../../../../state/features/persistSlice.ts";
import { RootState, store } from "../../../../state/store.ts";
import { useVideoPlayerState } from "../VideoPlayer-State.ts"; import { useVideoPlayerState } from "../VideoPlayer-State.ts";
import { VideoPlayerProps } from "../VideoPlayer.tsx"; import { VideoPlayerProps } from "../VideoPlayer.tsx";
@ -38,6 +30,7 @@ export const useVideoControlsState = (
progress, progress,
videoObjectFit, videoObjectFit,
canPlay, canPlay,
containerRef,
} = videoPlayerState; } = videoPlayerState;
const { identifier, autoPlay } = props; const { identifier, autoPlay } = props;
@ -78,16 +71,15 @@ export const useVideoControlsState = (
const isFullscreen = useSignal(false); const isFullscreen = useSignal(false);
const enterFullscreen = () => { const enterFullscreen = () => {
if (!videoRef.current) return; if (!containerRef.current) return;
if (videoRef.current.requestFullscreen) {
videoRef.current.requestFullscreen(); if (containerRef.current.requestFullscreen && !isFullscreen.value) {
containerRef.current.requestFullscreen();
} }
}; };
const exitFullscreen = () => { const exitFullscreen = () => {
if (document.exitFullscreen) { if (isFullscreen.value) document.exitFullscreen();
document.exitFullscreen();
}
}; };
const toggleFullscreen = () => { const toggleFullscreen = () => {
@ -218,14 +210,20 @@ export const useVideoControlsState = (
} }
}; };
const toggleStretchVideoSetting = () => { const setStretchVideoSetting = (value: "contain" | "fill") => {
const newStretchVideoSetting = videoObjectFit.value = value;
persistSelector.stretchVideoSetting === "contain" ? "fill" : "contain";
videoObjectFit.value = newStretchVideoSetting;
}; };
const keyboardShortcutsDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
const toggleStretchVideoSetting = () => {
videoObjectFit.value =
videoObjectFit.value === "contain" ? "fill" : "contain";
};
const keyboardShortcuts = (
e: KeyboardEvent | React.KeyboardEvent<HTMLDivElement>
) => {
e.preventDefault(); e.preventDefault();
// console.log("hotkey is: ", '"' + e.key + '"');
switch (e.key) { switch (e.key) {
case "o": case "o":
@ -276,13 +274,6 @@ export const useVideoControlsState = (
case Key.ArrowUp: case Key.ArrowUp:
changeVolume(0.05); changeVolume(0.05);
break; break;
}
};
const keyboardShortcutsUp = (e: React.KeyboardEvent<HTMLDivElement>) => {
e.preventDefault();
switch (e.key) {
case " ": case " ":
togglePlay(); togglePlay();
break; break;
@ -291,12 +282,20 @@ export const useVideoControlsState = (
break; break;
case "f": case "f":
enterFullscreen(); toggleFullscreen();
break; break;
case Key.Escape: case Key.Escape:
exitFullscreen(); exitFullscreen();
break; break;
case "r":
reloadVideo();
break;
case "p":
togglePictureInPicture();
break;
case "0": case "0":
setProgressAbsolute(0); setProgressAbsolute(0);
break; break;
@ -360,11 +359,12 @@ export const useVideoControlsState = (
increaseSpeed, increaseSpeed,
togglePictureInPicture, togglePictureInPicture,
toggleFullscreen, toggleFullscreen,
keyboardShortcutsUp, keyboardShortcuts,
keyboardShortcutsDown,
handleCanPlay, handleCanPlay,
toggleMute, toggleMute,
showControlsFullScreen, showControlsFullScreen,
setPlaying, setPlaying,
isFullscreen,
setStretchVideoSetting,
}; };
}; };

View File

@ -1,3 +1,8 @@
import { IconButton, Slider, Typography } from "@mui/material";
import { fontSizeExSmall, fontSizeSmall } from "../../../../constants/Misc.ts";
import { CustomFontTooltip } from "../../../../utils/CustomFontTooltip.tsx";
import { formatTime } from "../../../../utils/numberFunctions.ts";
import { useVideoContext } from "./VideoContext.ts";
import { import {
Fullscreen, Fullscreen,
Pause, Pause,
@ -7,142 +12,190 @@ import {
VolumeOff, VolumeOff,
VolumeUp, VolumeUp,
} from "@mui/icons-material"; } from "@mui/icons-material";
import { IconButton, Slider, Typography, useMediaQuery } from "@mui/material";
import { smallScreenSizeString } from "../../../../constants/Misc.ts";
import { formatTime } from "../../../../utils/numberFunctions.ts";
import { ControlsContainer } from "../VideoPlayer-styles.ts";
import { MobileControls } from "./MobileControls.tsx";
import { useVideoContext } from "./VideoContext.ts";
import { useSignalEffect } from "@preact/signals-react"; import { useSignalEffect } from "@preact/signals-react";
export const VideoControls = () => { export const PlayButton = () => {
const { const { togglePlay, playing } = useVideoContext();
reloadVideo, return (
togglePlay, <CustomFontTooltip title="Pause/Play (Spacebar)" placement="top" arrow>
onVolumeChange, <IconButton
increaseSpeed, sx={{
togglePictureInPicture, color: "white",
toggleFullscreen, }}
toggleMute, onClick={() => togglePlay()}
onProgressChange, >
toggleRef, {playing.value ? <Pause /> : <PlayArrow />}
from, </IconButton>
videoRef, </CustomFontTooltip>
canPlay, );
isMuted, };
playbackRate,
playing,
progress,
volume,
showControlsFullScreen,
} = useVideoContext();
const isScreenSmall = !useMediaQuery(`(min-width:580px)`); export const ReloadButton = () => {
const showMobileControls = isScreenSmall && canPlay.value; const { reloadVideo } = useVideoContext();
return (
<CustomFontTooltip title="Reload Video (R)" placement="top" arrow>
<IconButton
sx={{
color: "white",
}}
onClick={reloadVideo}
>
<Refresh />
</IconButton>
</CustomFontTooltip>
);
};
export const ProgressSlider = () => {
const { progress, onProgressChange, videoRef } = useVideoContext();
const sliderThumbSize = "16px";
return (
<Slider
value={progress.value}
onChange={onProgressChange}
min={0}
max={videoRef.current?.duration || 100}
sx={{
position: "absolute",
bottom: "40px",
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: sliderThumbSize,
height: sliderThumbSize,
},
"& .MuiSlider-rail": { opacity: 0.5 },
}}
/>
);
};
export const VideoTime = () => {
const { videoRef, progress, isScreenSmall } = useVideoContext();
return ( return (
<ControlsContainer <CustomFontTooltip
style={{ bottom: from === "create" ? "15px" : 0, padding: "0px" }} title="Seek video in 10% increments (0-9)"
display={showControlsFullScreen.value ? "flex" : "none"} placement="top"
arrow
> >
{showMobileControls ? ( <Typography
<MobileControls /> sx={{
) : canPlay.value ? ( fontSize: isScreenSmall ? fontSizeExSmall : fontSizeSmall,
<> color: "white",
<IconButton visibility: !videoRef.current?.duration ? "hidden" : "visible",
sx={{ whiteSpace: "nowrap",
color: "rgba(255, 255, 255, 0.7)", }}
}} >
onClick={() => togglePlay()} {videoRef.current?.duration ? formatTime(progress.value) : ""}
> {" / "}
{playing.value ? <Pause /> : <PlayArrow />} {videoRef.current?.duration
</IconButton> ? formatTime(videoRef.current?.duration)
<IconButton : ""}
sx={{ </Typography>
color: "rgba(255, 255, 255, 0.7)", </CustomFontTooltip>
marginLeft: "15px", );
}} };
onClick={reloadVideo}
>
<Refresh />
</IconButton>
<Slider
value={progress.value}
onChange={onProgressChange}
min={0}
max={videoRef.current?.duration || 100}
sx={{ flexGrow: 1, mx: 2 }}
/>
<Typography
sx={{
fontSize: "14px",
marginRight: "5px",
color: "rgba(255, 255, 255, 0.7)",
visibility:
!videoRef.current?.duration || !progress.value
? "hidden"
: "visible",
}}
>
{progress.value &&
videoRef.current?.duration &&
formatTime(progress.value)}
/
{progress.value &&
videoRef.current?.duration &&
formatTime(videoRef.current?.duration)}
</Typography>
<IconButton
sx={{
color: "rgba(255, 255, 255, 0.7)",
marginRight: "10px",
}}
onClick={toggleMute}
>
{isMuted.value ? <VolumeOff /> : <VolumeUp />}
</IconButton>
<Slider
value={volume.value}
onChange={onVolumeChange}
min={0}
max={1}
step={0.01}
sx={{
maxWidth: "100px",
}}
/>
<IconButton
sx={{
color: "rgba(255, 255, 255, 0.7)",
fontSize: "14px",
marginLeft: "5px",
}}
onClick={e => increaseSpeed()}
>
Speed: {playbackRate}x
</IconButton>
export const VolumeButton = () => {
const { isMuted, toggleMute } = useVideoContext();
return (
<CustomFontTooltip
title="Toggle Mute (M), Raise (UP), Lower (DOWN)"
placement="top"
arrow
>
<IconButton
sx={{
color: "white",
}}
onClick={toggleMute}
>
{isMuted.value ? <VolumeOff /> : <VolumeUp />}
</IconButton>
</CustomFontTooltip>
);
};
export const VolumeSlider = () => {
const { volume, onVolumeChange } = useVideoContext();
return (
<Slider
value={volume.value}
onChange={onVolumeChange}
min={0}
max={1}
step={0.01}
sx={{
width: "100px",
}}
/>
);
};
export const PlaybackRate = () => {
const { playbackRate, increaseSpeed, isScreenSmall } = useVideoContext();
return (
<CustomFontTooltip
title="Video Speed. Increase (+ or >), Decrease (- or <)"
placement="top"
arrow
>
<IconButton
sx={{
color: "white",
fontSize: isScreenSmall ? fontSizeExSmall : fontSizeSmall,
paddingTop: "0px",
paddingBottom: "0px",
}}
onClick={() => increaseSpeed()}
>
<span style={{ display: "flex", alignItems: "center", height: "40px" }}>
{playbackRate}x
</span>
</IconButton>
</CustomFontTooltip>
);
};
export const PictureInPictureButton = () => {
const { isFullscreen, toggleRef, togglePictureInPicture } = useVideoContext();
return (
<>
{!isFullscreen.value && (
<CustomFontTooltip title="Picture in Picture (P)" placement="top" arrow>
<IconButton <IconButton
sx={{ sx={{
color: "rgba(255, 255, 255, 0.7)", color: "white",
marginLeft: "15px",
}} }}
ref={toggleRef} ref={toggleRef}
onClick={togglePictureInPicture} onClick={togglePictureInPicture}
> >
<PictureInPicture /> <PictureInPicture />
</IconButton> </IconButton>
<IconButton </CustomFontTooltip>
sx={{ )}
color: "rgba(255, 255, 255, 0.7)", </>
}} );
onClick={toggleFullscreen} };
>
<Fullscreen /> export const FullscreenButton = () => {
</IconButton> const { toggleFullscreen } = useVideoContext();
</> return (
) : null} <CustomFontTooltip title="Toggle Fullscreen (F)" placement="top" arrow>
</ControlsContainer> <IconButton
sx={{
color: "white",
paddingRight: "0px",
}}
onClick={() => toggleFullscreen()}
>
<Fullscreen />
</IconButton>
</CustomFontTooltip>
); );
}; };

View File

@ -0,0 +1,62 @@
import { Box } from "@mui/material";
import { ControlsContainer } from "../VideoPlayer-styles.ts";
import { MobileControlsBar } from "./MobileControlsBar.tsx";
import { useVideoContext } from "./VideoContext.ts";
import {
FullscreenButton,
PictureInPictureButton,
PlaybackRate,
PlayButton,
ProgressSlider,
ReloadButton,
VideoTime,
VolumeButton,
VolumeSlider,
} from "./VideoControls.tsx";
import { useSignalEffect } from "@preact/signals-react";
export const VideoControlsBar = () => {
const { from, canPlay, showControlsFullScreen, isScreenSmall, progress } =
useVideoContext();
const showMobileControls = isScreenSmall && canPlay.value;
const controlsHeight = "40px";
const controlGroupSX = {
display: "flex",
gap: "5px",
alignItems: "center",
height: controlsHeight,
};
return (
<ControlsContainer
style={{
padding: "0px",
height: controlsHeight,
}}
>
{showMobileControls ? (
<MobileControlsBar />
) : canPlay.value ? (
<>
<Box sx={controlGroupSX}>
<PlayButton />
<ReloadButton />
<ProgressSlider />
<VolumeButton />
<VolumeSlider />
<VideoTime />
</Box>
<Box sx={controlGroupSX}>
<PlaybackRate />
<PictureInPictureButton />
<FullscreenButton />
</Box>
</>
) : null}
</ControlsContainer>
);
};

View File

@ -1,3 +1,4 @@
import { useMediaQuery } from "@mui/material";
import { import {
useSignal, useSignal,
useSignalEffect, useSignalEffect,
@ -12,6 +13,7 @@ import React, {
} from "react"; } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { smallVideoSize } from "../../../constants/Misc.ts";
import { setVideoPlaying } from "../../../state/features/globalSlice.ts"; import { setVideoPlaying } from "../../../state/features/globalSlice.ts";
import { import {
setIsMuted, setIsMuted,
@ -292,6 +294,7 @@ export const useVideoPlayerState = (props: VideoPlayerProps, ref: any) => {
anchorEl.value = null; anchorEl.value = null;
}; };
const isScreenSmall = !useMediaQuery(smallVideoSize);
return { return {
containerRef, containerRef,
resourceStatus, resourceStatus,
@ -315,5 +318,6 @@ export const useVideoPlayerState = (props: VideoPlayerProps, ref: any) => {
playbackRate, playbackRate,
anchorEl, anchorEl,
videoObjectFit, videoObjectFit,
isScreenSmall,
}; };
}; };

View File

@ -20,12 +20,9 @@ export const VideoElement = styled("video")(({ theme }) => ({
})); }));
//1075 x 604 //1075 x 604
export const ControlsContainer = styled(Box)` export const ControlsContainer = styled(Box)`
position: absolute; width: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.6); background-color: rgba(0, 0, 0, 0.6);
`; `;

View File

@ -1,8 +1,9 @@
import CSS from "csstype"; import CSS from "csstype";
import { forwardRef } from "react"; import { forwardRef } from "react";
import useIdleTimeout from "../../../hooks/useIdleTimeout.ts";
import { LoadingVideo } from "./Components/LoadingVideo.tsx"; import { LoadingVideo } from "./Components/LoadingVideo.tsx";
import { useContextData, VideoContext } from "./Components/VideoContext.ts"; import { useContextData, VideoContext } from "./Components/VideoContext.ts";
import { VideoControls } from "./Components/VideoControls.tsx"; import { VideoControlsBar } from "./Components/VideoControlsBar.tsx";
import { VideoContainer, VideoElement } from "./VideoPlayer-styles.ts"; import { VideoContainer, VideoElement } from "./VideoPlayer-styles.ts";
export interface VideoStyles { export interface VideoStyles {
@ -37,8 +38,7 @@ export const VideoPlayer = forwardRef<videoRefType, VideoPlayerProps>(
const contextData = useContextData(props, ref); const contextData = useContextData(props, ref);
const { const {
keyboardShortcutsUp, keyboardShortcuts,
keyboardShortcutsDown,
from, from,
videoStyles, videoStyles,
containerRef, containerRef,
@ -55,18 +55,35 @@ export const VideoPlayer = forwardRef<videoRefType, VideoPlayerProps>(
startPlay, startPlay,
videoObjectFit, videoObjectFit,
showControlsFullScreen, showControlsFullScreen,
isFullscreen,
} = contextData; } = contextData;
const showControls =
!isFullscreen.value ||
(isFullscreen.value && showControlsFullScreen.value);
const idleTime = 5000; // Time in milliseconds
useIdleTimeout({
onIdle: () => (showControlsFullScreen.value = false),
onActive: () => (showControlsFullScreen.value = true),
idleTime,
});
return ( return (
<VideoContext.Provider value={contextData}> <VideoContext.Provider value={contextData}>
<VideoContainer <VideoContainer
tabIndex={0} tabIndex={0}
onKeyUp={keyboardShortcutsUp} onKeyDown={keyboardShortcuts}
onKeyDown={keyboardShortcutsDown}
style={{ style={{
padding: from === "create" ? "8px" : 0, padding: from === "create" ? "8px" : 0,
...videoStyles?.videoContainer, ...videoStyles?.videoContainer,
}} }}
onMouseEnter={e => {
showControlsFullScreen.value = true;
}}
onMouseLeave={e => {
showControlsFullScreen.value = false;
}}
ref={containerRef} ref={containerRef}
> >
<LoadingVideo /> <LoadingVideo />
@ -83,23 +100,17 @@ export const VideoPlayer = forwardRef<videoRefType, VideoPlayerProps>(
onEnded={handleEnded} onEnded={handleEnded}
// onLoadedMetadata={handleLoadedMetadata} // onLoadedMetadata={handleLoadedMetadata}
onCanPlay={handleCanPlay} onCanPlay={handleCanPlay}
onMouseEnter={e => {
showControlsFullScreen.value = true;
}}
onMouseLeave={e => {
showControlsFullScreen.value = false;
}}
preload="metadata" preload="metadata"
style={ style={{
startPlay.value ...videoStyles?.video,
? { objectFit: isFullscreen ? "fill" : videoObjectFit.value,
...videoStyles?.video, height:
objectFit: videoObjectFit.value, isFullscreen.value && showControlsFullScreen.value
} ? "calc(100vh - 40px)"
: { height: "100%", ...videoStyles } : "100%",
} }}
/> />
<VideoControls /> {showControls && <VideoControlsBar />}
</VideoContainer> </VideoContainer>
</VideoContext.Provider> </VideoContext.Provider>
); );

View File

@ -21,5 +21,6 @@ const largeScreenSize = 1400 - newUIWidthDiff;
export const smallScreenSizeString = `${smallScreenSize}px`; export const smallScreenSizeString = `${smallScreenSize}px`;
export const largeScreenSizeString = `${largeScreenSize}px`; export const largeScreenSizeString = `${largeScreenSize}px`;
export const smallVideoSize = `(min-width:720px)`;
export const headerIconSize = "40px"; export const headerIconSize = "40px";
export const menuIconSize = "28px"; export const menuIconSize = "28px";

View File

@ -0,0 +1,14 @@
import { useContext, useState } from "react";
import { useIdleTimer } from "react-idle-timer";
const useIdleTimeout = ({ onIdle, onActive, idleTime = 10_000 }) => {
const idleTimer = useIdleTimer({
timeout: idleTime,
onIdle: onIdle,
onActive: onActive,
});
return {
idleTimer,
};
};
export default useIdleTimeout;

View File

@ -11,6 +11,7 @@ import {
largeScreenSizeString, largeScreenSizeString,
minFileSize, minFileSize,
smallScreenSizeString, smallScreenSizeString,
smallVideoSize,
} from "../../../constants/Misc.ts"; } from "../../../constants/Misc.ts";
import { useIsMobile } from "../../../hooks/useIsMobile.ts"; import { useIsMobile } from "../../../hooks/useIsMobile.ts";
import { formatBytes } from "../../../utils/numberFunctions.ts"; import { formatBytes } from "../../../utils/numberFunctions.ts";
@ -47,7 +48,7 @@ export const VideoContent = () => {
setSuperLikeList, setSuperLikeList,
} = useVideoContentState(); } = useVideoContentState();
const isScreenSmall = !useMediaQuery(`(min-width:${smallScreenSizeString})`); const isScreenSmall = !useMediaQuery(smallVideoSize);
const [screenWidth, setScreenWidth] = useState<number>( const [screenWidth, setScreenWidth] = useState<number>(
window.innerWidth + 120 window.innerWidth + 120
); );
@ -75,7 +76,7 @@ export const VideoContent = () => {
sx={{ sx={{
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
padding: `0px 0px 0px ${isScreenSmall ? "5px" : "2%"}`, padding: `0px 0px 0px ${isScreenSmall ? "0px" : "2%"}`,
width: "100%", width: "100%",
}} }}
onClick={focusVideo} onClick={focusVideo}
@ -112,7 +113,9 @@ export const VideoContent = () => {
) : ( ) : (
<Box sx={{ width: "55vw", aspectRatio: "16/9" }}></Box> <Box sx={{ width: "55vw", aspectRatio: "16/9" }}></Box>
)} )}
<VideoContentContainer> <VideoContentContainer
sx={{ paddingLeft: isScreenSmall ? "5px" : "0px" }}
>
<VideoTitle <VideoTitle
variant={isScreenSmall ? "h2" : "h1"} variant={isScreenSmall ? "h2" : "h1"}
color="textPrimary" color="textPrimary"

View File

@ -1,20 +1,13 @@
import { TabContext, TabList, TabPanel } from "@mui/lab"; import { TabContext, TabList, TabPanel } from "@mui/lab";
import { Box, Grid, Tab, useMediaQuery } from "@mui/material"; import { Box, Tab, useMediaQuery } from "@mui/material";
import React from "react"; import React from "react";
import LazyLoad from "../../components/common/LazyLoad"; import LazyLoad from "../../components/common/LazyLoad";
import { ListSuperLikeContainer } from "../../components/common/ListSuperLikes/ListSuperLikeContainer.tsx"; import { ListSuperLikeContainer } from "../../components/common/ListSuperLikes/ListSuperLikeContainer.tsx";
import { import { fontSizeLarge, fontSizeSmall } from "../../constants/Misc.ts";
fontSizeLarge,
fontSizeMedium,
fontSizeSmall,
} from "../../constants/Misc.ts";
import { useIsMobile } from "../../hooks/useIsMobile.ts";
import { SearchSidebar } from "./Components/SearchSidebar.tsx"; import { SearchSidebar } from "./Components/SearchSidebar.tsx";
import { FiltersCol, VideoManagerRow } from "./Components/VideoList-styles.tsx";
import VideoList from "./Components/VideoList.tsx"; import VideoList from "./Components/VideoList.tsx";
import { useHomeState } from "./Home-State.ts"; import { useHomeState } from "./Home-State.ts";
import { SubtitleContainer } from "./Home-styles";
interface HomeProps { interface HomeProps {
mode?: string; mode?: string;

View File

@ -0,0 +1,23 @@
import { Box, Tooltip, TooltipProps } from "@mui/material";
import { PropsWithChildren } from "react";
import { fontSizeSmall } from "../constants/Misc";
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>
);
};

View File

@ -1,8 +1,9 @@
import { defineConfig } from 'vite' import { defineConfig } from "vite";
import react from '@vitejs/plugin-react' import react from "@vitejs/plugin-react";
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [react()],
base: "" server: { port: 3000 },
}) base: "",
});