mirror of
https://github.com/Qortal/q-tube.git
synced 2025-02-11 09:45:51 +00:00
Updates to VideoPlayer controls
All controls and hotkeys work when the VideoPlayer is fullscreen Controls are below video instead of inside of it Controls have tooltips showing what they do and their hotkeys Each control is a separate component that is used in both mobile and normal controls Video Progress slider is above controls to save horizontal space Controls will disappear when fullscreen if mouse leaves video player, or after 5 seconds of inactivity Default port in vite.config.ts set to 3000 for simplicity.
This commit is contained in:
parent
29de8a8a9f
commit
53eaad448b
20
package-lock.json
generated
20
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "qtube",
|
||||
"version": "2.0.0",
|
||||
"version": "2.1.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "qtube",
|
||||
"version": "2.0.0",
|
||||
"version": "2.1.0",
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.10.6",
|
||||
"@emotion/styled": "^11.10.6",
|
||||
@ -23,6 +23,7 @@
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-idle-timer": "^5.7.2",
|
||||
"react-intersection-observer": "^9.4.3",
|
||||
"react-quill": "^2.0.0",
|
||||
"react-redux": "^8.0.5",
|
||||
@ -3958,6 +3959,15 @@
|
||||
"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": {
|
||||
"version": "9.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.5.0.tgz",
|
||||
@ -7321,6 +7331,12 @@
|
||||
"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": {
|
||||
"version": "9.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.5.0.tgz",
|
||||
|
@ -25,6 +25,7 @@
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-idle-timer": "^5.7.2",
|
||||
"react-intersection-observer": "^9.4.3",
|
||||
"react-quill": "^2.0.0",
|
||||
"react-redux": "^8.0.5",
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
@ -5,16 +5,8 @@ import { useEffect } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { Key } from "ts-key-enum";
|
||||
import { useIsMobile } from "../../../../hooks/useIsMobile.ts";
|
||||
import { setVideoPlaying } from "../../../../state/features/globalSlice.ts";
|
||||
import {
|
||||
setIsMuted,
|
||||
setMutedVolumeSetting,
|
||||
setReduxPlaybackRate,
|
||||
setStretchVideoSetting,
|
||||
setVolumeSetting,
|
||||
} from "../../../../state/features/persistSlice.ts";
|
||||
import { RootState, store } from "../../../../state/store.ts";
|
||||
import { RootState } from "../../../../state/store.ts";
|
||||
import { useVideoPlayerState } from "../VideoPlayer-State.ts";
|
||||
import { VideoPlayerProps } from "../VideoPlayer.tsx";
|
||||
|
||||
@ -38,6 +30,7 @@ export const useVideoControlsState = (
|
||||
progress,
|
||||
videoObjectFit,
|
||||
canPlay,
|
||||
containerRef,
|
||||
} = videoPlayerState;
|
||||
const { identifier, autoPlay } = props;
|
||||
|
||||
@ -78,16 +71,15 @@ export const useVideoControlsState = (
|
||||
const isFullscreen = useSignal(false);
|
||||
|
||||
const enterFullscreen = () => {
|
||||
if (!videoRef.current) return;
|
||||
if (videoRef.current.requestFullscreen) {
|
||||
videoRef.current.requestFullscreen();
|
||||
if (!containerRef.current) return;
|
||||
|
||||
if (containerRef.current.requestFullscreen && !isFullscreen.value) {
|
||||
containerRef.current.requestFullscreen();
|
||||
}
|
||||
};
|
||||
|
||||
const exitFullscreen = () => {
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen();
|
||||
}
|
||||
if (isFullscreen.value) document.exitFullscreen();
|
||||
};
|
||||
|
||||
const toggleFullscreen = () => {
|
||||
@ -218,14 +210,20 @@ export const useVideoControlsState = (
|
||||
}
|
||||
};
|
||||
|
||||
const toggleStretchVideoSetting = () => {
|
||||
const newStretchVideoSetting =
|
||||
persistSelector.stretchVideoSetting === "contain" ? "fill" : "contain";
|
||||
|
||||
videoObjectFit.value = newStretchVideoSetting;
|
||||
const setStretchVideoSetting = (value: "contain" | "fill") => {
|
||||
videoObjectFit.value = value;
|
||||
};
|
||||
const keyboardShortcutsDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
|
||||
const toggleStretchVideoSetting = () => {
|
||||
videoObjectFit.value =
|
||||
videoObjectFit.value === "contain" ? "fill" : "contain";
|
||||
};
|
||||
|
||||
const keyboardShortcuts = (
|
||||
e: KeyboardEvent | React.KeyboardEvent<HTMLDivElement>
|
||||
) => {
|
||||
e.preventDefault();
|
||||
// console.log("hotkey is: ", '"' + e.key + '"');
|
||||
|
||||
switch (e.key) {
|
||||
case "o":
|
||||
@ -276,13 +274,6 @@ export const useVideoControlsState = (
|
||||
case Key.ArrowUp:
|
||||
changeVolume(0.05);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const keyboardShortcutsUp = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
switch (e.key) {
|
||||
case " ":
|
||||
togglePlay();
|
||||
break;
|
||||
@ -291,12 +282,20 @@ export const useVideoControlsState = (
|
||||
break;
|
||||
|
||||
case "f":
|
||||
enterFullscreen();
|
||||
toggleFullscreen();
|
||||
break;
|
||||
case Key.Escape:
|
||||
exitFullscreen();
|
||||
break;
|
||||
|
||||
case "r":
|
||||
reloadVideo();
|
||||
break;
|
||||
|
||||
case "p":
|
||||
togglePictureInPicture();
|
||||
break;
|
||||
|
||||
case "0":
|
||||
setProgressAbsolute(0);
|
||||
break;
|
||||
@ -360,11 +359,12 @@ export const useVideoControlsState = (
|
||||
increaseSpeed,
|
||||
togglePictureInPicture,
|
||||
toggleFullscreen,
|
||||
keyboardShortcutsUp,
|
||||
keyboardShortcutsDown,
|
||||
keyboardShortcuts,
|
||||
handleCanPlay,
|
||||
toggleMute,
|
||||
showControlsFullScreen,
|
||||
setPlaying,
|
||||
isFullscreen,
|
||||
setStretchVideoSetting,
|
||||
};
|
||||
};
|
||||
|
@ -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 {
|
||||
Fullscreen,
|
||||
Pause,
|
||||
@ -7,101 +12,118 @@ import {
|
||||
VolumeOff,
|
||||
VolumeUp,
|
||||
} 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";
|
||||
|
||||
export const VideoControls = () => {
|
||||
const {
|
||||
reloadVideo,
|
||||
togglePlay,
|
||||
onVolumeChange,
|
||||
increaseSpeed,
|
||||
togglePictureInPicture,
|
||||
toggleFullscreen,
|
||||
toggleMute,
|
||||
onProgressChange,
|
||||
toggleRef,
|
||||
from,
|
||||
videoRef,
|
||||
canPlay,
|
||||
isMuted,
|
||||
playbackRate,
|
||||
playing,
|
||||
progress,
|
||||
volume,
|
||||
showControlsFullScreen,
|
||||
} = useVideoContext();
|
||||
|
||||
const isScreenSmall = !useMediaQuery(`(min-width:580px)`);
|
||||
const showMobileControls = isScreenSmall && canPlay.value;
|
||||
|
||||
export const PlayButton = () => {
|
||||
const { togglePlay, playing } = useVideoContext();
|
||||
return (
|
||||
<ControlsContainer
|
||||
style={{ bottom: from === "create" ? "15px" : 0, padding: "0px" }}
|
||||
display={showControlsFullScreen.value ? "flex" : "none"}
|
||||
>
|
||||
{showMobileControls ? (
|
||||
<MobileControls />
|
||||
) : canPlay.value ? (
|
||||
<>
|
||||
<CustomFontTooltip title="Pause/Play (Spacebar)" placement="top" arrow>
|
||||
<IconButton
|
||||
sx={{
|
||||
color: "rgba(255, 255, 255, 0.7)",
|
||||
color: "white",
|
||||
}}
|
||||
onClick={() => togglePlay()}
|
||||
>
|
||||
{playing.value ? <Pause /> : <PlayArrow />}
|
||||
</IconButton>
|
||||
</CustomFontTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export const ReloadButton = () => {
|
||||
const { reloadVideo } = useVideoContext();
|
||||
return (
|
||||
<CustomFontTooltip title="Reload Video (R)" placement="top" arrow>
|
||||
<IconButton
|
||||
sx={{
|
||||
color: "rgba(255, 255, 255, 0.7)",
|
||||
marginLeft: "15px",
|
||||
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={{ flexGrow: 1, mx: 2 }}
|
||||
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 (
|
||||
<CustomFontTooltip
|
||||
title="Seek video in 10% increments (0-9)"
|
||||
placement="top"
|
||||
arrow
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
marginRight: "5px",
|
||||
color: "rgba(255, 255, 255, 0.7)",
|
||||
visibility:
|
||||
!videoRef.current?.duration || !progress.value
|
||||
? "hidden"
|
||||
: "visible",
|
||||
fontSize: isScreenSmall ? fontSizeExSmall : fontSizeSmall,
|
||||
color: "white",
|
||||
visibility: !videoRef.current?.duration ? "hidden" : "visible",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
{progress.value &&
|
||||
videoRef.current?.duration &&
|
||||
formatTime(progress.value)}
|
||||
/
|
||||
{progress.value &&
|
||||
videoRef.current?.duration &&
|
||||
formatTime(videoRef.current?.duration)}
|
||||
{videoRef.current?.duration ? formatTime(progress.value) : ""}
|
||||
{" / "}
|
||||
{videoRef.current?.duration
|
||||
? formatTime(videoRef.current?.duration)
|
||||
: ""}
|
||||
</Typography>
|
||||
</CustomFontTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export const VolumeButton = () => {
|
||||
const { isMuted, toggleMute } = useVideoContext();
|
||||
return (
|
||||
<CustomFontTooltip
|
||||
title="Toggle Mute (M), Raise (UP), Lower (DOWN)"
|
||||
placement="top"
|
||||
arrow
|
||||
>
|
||||
<IconButton
|
||||
sx={{
|
||||
color: "rgba(255, 255, 255, 0.7)",
|
||||
marginRight: "10px",
|
||||
color: "white",
|
||||
}}
|
||||
onClick={toggleMute}
|
||||
>
|
||||
{isMuted.value ? <VolumeOff /> : <VolumeUp />}
|
||||
</IconButton>
|
||||
</CustomFontTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export const VolumeSlider = () => {
|
||||
const { volume, onVolumeChange } = useVideoContext();
|
||||
return (
|
||||
<Slider
|
||||
value={volume.value}
|
||||
onChange={onVolumeChange}
|
||||
@ -109,40 +131,71 @@ export const VideoControls = () => {
|
||||
max={1}
|
||||
step={0.01}
|
||||
sx={{
|
||||
maxWidth: "100px",
|
||||
width: "100px",
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
sx={{
|
||||
color: "rgba(255, 255, 255, 0.7)",
|
||||
fontSize: "14px",
|
||||
marginLeft: "5px",
|
||||
}}
|
||||
onClick={e => increaseSpeed()}
|
||||
>
|
||||
Speed: {playbackRate}x
|
||||
</IconButton>
|
||||
);
|
||||
};
|
||||
|
||||
export const PlaybackRate = () => {
|
||||
const { playbackRate, increaseSpeed, isScreenSmall } = useVideoContext();
|
||||
return (
|
||||
<CustomFontTooltip
|
||||
title="Video Speed. Increase (+ or >), Decrease (- or <)"
|
||||
placement="top"
|
||||
arrow
|
||||
>
|
||||
<IconButton
|
||||
sx={{
|
||||
color: "rgba(255, 255, 255, 0.7)",
|
||||
marginLeft: "15px",
|
||||
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
|
||||
sx={{
|
||||
color: "white",
|
||||
}}
|
||||
ref={toggleRef}
|
||||
onClick={togglePictureInPicture}
|
||||
>
|
||||
<PictureInPicture />
|
||||
</IconButton>
|
||||
</CustomFontTooltip>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const FullscreenButton = () => {
|
||||
const { toggleFullscreen } = useVideoContext();
|
||||
return (
|
||||
<CustomFontTooltip title="Toggle Fullscreen (F)" placement="top" arrow>
|
||||
<IconButton
|
||||
sx={{
|
||||
color: "rgba(255, 255, 255, 0.7)",
|
||||
color: "white",
|
||||
paddingRight: "0px",
|
||||
}}
|
||||
onClick={toggleFullscreen}
|
||||
onClick={() => toggleFullscreen()}
|
||||
>
|
||||
<Fullscreen />
|
||||
</IconButton>
|
||||
</>
|
||||
) : null}
|
||||
</ControlsContainer>
|
||||
</CustomFontTooltip>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
@ -1,3 +1,4 @@
|
||||
import { useMediaQuery } from "@mui/material";
|
||||
import {
|
||||
useSignal,
|
||||
useSignalEffect,
|
||||
@ -12,6 +13,7 @@ import React, {
|
||||
} from "react";
|
||||
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { smallVideoSize } from "../../../constants/Misc.ts";
|
||||
import { setVideoPlaying } from "../../../state/features/globalSlice.ts";
|
||||
import {
|
||||
setIsMuted,
|
||||
@ -292,6 +294,7 @@ export const useVideoPlayerState = (props: VideoPlayerProps, ref: any) => {
|
||||
anchorEl.value = null;
|
||||
};
|
||||
|
||||
const isScreenSmall = !useMediaQuery(smallVideoSize);
|
||||
return {
|
||||
containerRef,
|
||||
resourceStatus,
|
||||
@ -315,5 +318,6 @@ export const useVideoPlayerState = (props: VideoPlayerProps, ref: any) => {
|
||||
playbackRate,
|
||||
anchorEl,
|
||||
videoObjectFit,
|
||||
isScreenSmall,
|
||||
};
|
||||
};
|
||||
|
@ -20,12 +20,9 @@ export const VideoElement = styled("video")(({ theme }) => ({
|
||||
}));
|
||||
//1075 x 604
|
||||
export const ControlsContainer = styled(Box)`
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
`;
|
||||
|
@ -1,8 +1,9 @@
|
||||
import CSS from "csstype";
|
||||
import { forwardRef } from "react";
|
||||
import useIdleTimeout from "../../../hooks/useIdleTimeout.ts";
|
||||
import { LoadingVideo } from "./Components/LoadingVideo.tsx";
|
||||
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";
|
||||
|
||||
export interface VideoStyles {
|
||||
@ -37,8 +38,7 @@ export const VideoPlayer = forwardRef<videoRefType, VideoPlayerProps>(
|
||||
const contextData = useContextData(props, ref);
|
||||
|
||||
const {
|
||||
keyboardShortcutsUp,
|
||||
keyboardShortcutsDown,
|
||||
keyboardShortcuts,
|
||||
from,
|
||||
videoStyles,
|
||||
containerRef,
|
||||
@ -55,18 +55,35 @@ export const VideoPlayer = forwardRef<videoRefType, VideoPlayerProps>(
|
||||
startPlay,
|
||||
videoObjectFit,
|
||||
showControlsFullScreen,
|
||||
isFullscreen,
|
||||
} = 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 (
|
||||
<VideoContext.Provider value={contextData}>
|
||||
<VideoContainer
|
||||
tabIndex={0}
|
||||
onKeyUp={keyboardShortcutsUp}
|
||||
onKeyDown={keyboardShortcutsDown}
|
||||
onKeyDown={keyboardShortcuts}
|
||||
style={{
|
||||
padding: from === "create" ? "8px" : 0,
|
||||
...videoStyles?.videoContainer,
|
||||
}}
|
||||
onMouseEnter={e => {
|
||||
showControlsFullScreen.value = true;
|
||||
}}
|
||||
onMouseLeave={e => {
|
||||
showControlsFullScreen.value = false;
|
||||
}}
|
||||
ref={containerRef}
|
||||
>
|
||||
<LoadingVideo />
|
||||
@ -83,23 +100,17 @@ export const VideoPlayer = forwardRef<videoRefType, VideoPlayerProps>(
|
||||
onEnded={handleEnded}
|
||||
// onLoadedMetadata={handleLoadedMetadata}
|
||||
onCanPlay={handleCanPlay}
|
||||
onMouseEnter={e => {
|
||||
showControlsFullScreen.value = true;
|
||||
}}
|
||||
onMouseLeave={e => {
|
||||
showControlsFullScreen.value = false;
|
||||
}}
|
||||
preload="metadata"
|
||||
style={
|
||||
startPlay.value
|
||||
? {
|
||||
style={{
|
||||
...videoStyles?.video,
|
||||
objectFit: videoObjectFit.value,
|
||||
}
|
||||
: { height: "100%", ...videoStyles }
|
||||
}
|
||||
objectFit: isFullscreen ? "fill" : videoObjectFit.value,
|
||||
height:
|
||||
isFullscreen.value && showControlsFullScreen.value
|
||||
? "calc(100vh - 40px)"
|
||||
: "100%",
|
||||
}}
|
||||
/>
|
||||
<VideoControls />
|
||||
{showControls && <VideoControlsBar />}
|
||||
</VideoContainer>
|
||||
</VideoContext.Provider>
|
||||
);
|
||||
|
@ -21,5 +21,6 @@ const largeScreenSize = 1400 - newUIWidthDiff;
|
||||
export const smallScreenSizeString = `${smallScreenSize}px`;
|
||||
export const largeScreenSizeString = `${largeScreenSize}px`;
|
||||
|
||||
export const smallVideoSize = `(min-width:720px)`;
|
||||
export const headerIconSize = "40px";
|
||||
export const menuIconSize = "28px";
|
||||
|
14
src/hooks/useIdleTimeout.ts
Normal file
14
src/hooks/useIdleTimeout.ts
Normal 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;
|
@ -11,6 +11,7 @@ import {
|
||||
largeScreenSizeString,
|
||||
minFileSize,
|
||||
smallScreenSizeString,
|
||||
smallVideoSize,
|
||||
} from "../../../constants/Misc.ts";
|
||||
import { useIsMobile } from "../../../hooks/useIsMobile.ts";
|
||||
import { formatBytes } from "../../../utils/numberFunctions.ts";
|
||||
@ -47,7 +48,7 @@ export const VideoContent = () => {
|
||||
setSuperLikeList,
|
||||
} = useVideoContentState();
|
||||
|
||||
const isScreenSmall = !useMediaQuery(`(min-width:${smallScreenSizeString})`);
|
||||
const isScreenSmall = !useMediaQuery(smallVideoSize);
|
||||
const [screenWidth, setScreenWidth] = useState<number>(
|
||||
window.innerWidth + 120
|
||||
);
|
||||
@ -75,7 +76,7 @@ export const VideoContent = () => {
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
padding: `0px 0px 0px ${isScreenSmall ? "5px" : "2%"}`,
|
||||
padding: `0px 0px 0px ${isScreenSmall ? "0px" : "2%"}`,
|
||||
width: "100%",
|
||||
}}
|
||||
onClick={focusVideo}
|
||||
@ -112,7 +113,9 @@ export const VideoContent = () => {
|
||||
) : (
|
||||
<Box sx={{ width: "55vw", aspectRatio: "16/9" }}></Box>
|
||||
)}
|
||||
<VideoContentContainer>
|
||||
<VideoContentContainer
|
||||
sx={{ paddingLeft: isScreenSmall ? "5px" : "0px" }}
|
||||
>
|
||||
<VideoTitle
|
||||
variant={isScreenSmall ? "h2" : "h1"}
|
||||
color="textPrimary"
|
||||
|
@ -1,20 +1,13 @@
|
||||
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 LazyLoad from "../../components/common/LazyLoad";
|
||||
import { ListSuperLikeContainer } from "../../components/common/ListSuperLikes/ListSuperLikeContainer.tsx";
|
||||
import {
|
||||
fontSizeLarge,
|
||||
fontSizeMedium,
|
||||
fontSizeSmall,
|
||||
} from "../../constants/Misc.ts";
|
||||
import { useIsMobile } from "../../hooks/useIsMobile.ts";
|
||||
import { fontSizeLarge, fontSizeSmall } from "../../constants/Misc.ts";
|
||||
import { SearchSidebar } from "./Components/SearchSidebar.tsx";
|
||||
import { FiltersCol, VideoManagerRow } from "./Components/VideoList-styles.tsx";
|
||||
import VideoList from "./Components/VideoList.tsx";
|
||||
import { useHomeState } from "./Home-State.ts";
|
||||
import { SubtitleContainer } from "./Home-styles";
|
||||
|
||||
interface HomeProps {
|
||||
mode?: string;
|
||||
|
23
src/utils/CustomFontTooltip.tsx
Normal file
23
src/utils/CustomFontTooltip.tsx
Normal 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>
|
||||
);
|
||||
};
|
@ -1,8 +1,9 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
base: ""
|
||||
})
|
||||
server: { port: 3000 },
|
||||
base: "",
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user