mirror of
https://github.com/Qortal/q-tube.git
synced 2025-02-11 09:45:51 +00:00
Merge pull request #55 from QortalSeth/main
Updates to VideoPlayer controls
This commit is contained in:
commit
84c07b07cd
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,142 +12,190 @@ 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();
|
||||
export const PlayButton = () => {
|
||||
const { togglePlay, playing } = useVideoContext();
|
||||
return (
|
||||
<CustomFontTooltip title="Pause/Play (Spacebar)" placement="top" arrow>
|
||||
<IconButton
|
||||
sx={{
|
||||
color: "white",
|
||||
}}
|
||||
onClick={() => togglePlay()}
|
||||
>
|
||||
{playing.value ? <Pause /> : <PlayArrow />}
|
||||
</IconButton>
|
||||
</CustomFontTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const isScreenSmall = !useMediaQuery(`(min-width:580px)`);
|
||||
const showMobileControls = isScreenSmall && canPlay.value;
|
||||
export const ReloadButton = () => {
|
||||
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 (
|
||||
<ControlsContainer
|
||||
style={{ bottom: from === "create" ? "15px" : 0, padding: "0px" }}
|
||||
display={showControlsFullScreen.value ? "flex" : "none"}
|
||||
<CustomFontTooltip
|
||||
title="Seek video in 10% increments (0-9)"
|
||||
placement="top"
|
||||
arrow
|
||||
>
|
||||
{showMobileControls ? (
|
||||
<MobileControls />
|
||||
) : canPlay.value ? (
|
||||
<>
|
||||
<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)",
|
||||
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>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: isScreenSmall ? fontSizeExSmall : fontSizeSmall,
|
||||
color: "white",
|
||||
visibility: !videoRef.current?.duration ? "hidden" : "visible",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
{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: "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
|
||||
sx={{
|
||||
color: "rgba(255, 255, 255, 0.7)",
|
||||
marginLeft: "15px",
|
||||
color: "white",
|
||||
}}
|
||||
ref={toggleRef}
|
||||
onClick={togglePictureInPicture}
|
||||
>
|
||||
<PictureInPicture />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
sx={{
|
||||
color: "rgba(255, 255, 255, 0.7)",
|
||||
}}
|
||||
onClick={toggleFullscreen}
|
||||
>
|
||||
<Fullscreen />
|
||||
</IconButton>
|
||||
</>
|
||||
) : null}
|
||||
</ControlsContainer>
|
||||
</CustomFontTooltip>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const FullscreenButton = () => {
|
||||
const { toggleFullscreen } = useVideoContext();
|
||||
return (
|
||||
<CustomFontTooltip title="Toggle Fullscreen (F)" placement="top" arrow>
|
||||
<IconButton
|
||||
sx={{
|
||||
color: "white",
|
||||
paddingRight: "0px",
|
||||
}}
|
||||
onClick={() => toggleFullscreen()}
|
||||
>
|
||||
<Fullscreen />
|
||||
</IconButton>
|
||||
</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
|
||||
? {
|
||||
...videoStyles?.video,
|
||||
objectFit: videoObjectFit.value,
|
||||
}
|
||||
: { height: "100%", ...videoStyles }
|
||||
}
|
||||
style={{
|
||||
...videoStyles?.video,
|
||||
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