diff --git a/package-lock.json b/package-lock.json
index fc3ff92..5c70df0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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",
diff --git a/package.json b/package.json
index 0e9e6af..7bda89b 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/components/common/VideoPlayer/Components/MobileControls.tsx b/src/components/common/VideoPlayer/Components/MobileControls.tsx
deleted file mode 100644
index d5c8a39..0000000
--- a/src/components/common/VideoPlayer/Components/MobileControls.tsx
+++ /dev/null
@@ -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 (
- <>
- togglePlay()}
- >
- {playing.value ? : }
-
-
-
-
-
- increaseSpeed()}
- >
- {playbackRate}x
-
-
-
-
-
-
-
-
- >
- );
-};
diff --git a/src/components/common/VideoPlayer/Components/MobileControlsBar.tsx b/src/components/common/VideoPlayer/Components/MobileControlsBar.tsx
new file mode 100644
index 0000000..6376afc
--- /dev/null
+++ b/src/components/common/VideoPlayer/Components/MobileControlsBar.tsx
@@ -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 (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
diff --git a/src/components/common/VideoPlayer/Components/VideoControls-State.ts b/src/components/common/VideoPlayer/Components/VideoControls-State.ts
index 3ba70d1..ea0f92f 100644
--- a/src/components/common/VideoPlayer/Components/VideoControls-State.ts
+++ b/src/components/common/VideoPlayer/Components/VideoControls-State.ts
@@ -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) => {
+
+ const toggleStretchVideoSetting = () => {
+ videoObjectFit.value =
+ videoObjectFit.value === "contain" ? "fill" : "contain";
+ };
+
+ const keyboardShortcuts = (
+ e: KeyboardEvent | React.KeyboardEvent
+ ) => {
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) => {
- 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,
};
};
diff --git a/src/components/common/VideoPlayer/Components/VideoControls.tsx b/src/components/common/VideoPlayer/Components/VideoControls.tsx
index 93e620b..9702a1f 100644
--- a/src/components/common/VideoPlayer/Components/VideoControls.tsx
+++ b/src/components/common/VideoPlayer/Components/VideoControls.tsx
@@ -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 (
+
+ togglePlay()}
+ >
+ {playing.value ? : }
+
+
+ );
+};
- const isScreenSmall = !useMediaQuery(`(min-width:580px)`);
- const showMobileControls = isScreenSmall && canPlay.value;
+export const ReloadButton = () => {
+ const { reloadVideo } = useVideoContext();
+ return (
+
+
+
+
+
+ );
+};
+
+export const ProgressSlider = () => {
+ const { progress, onProgressChange, videoRef } = useVideoContext();
+ const sliderThumbSize = "16px";
+ return (
+
+ );
+};
+
+export const VideoTime = () => {
+ const { videoRef, progress, isScreenSmall } = useVideoContext();
return (
-
- {showMobileControls ? (
-
- ) : canPlay.value ? (
- <>
- togglePlay()}
- >
- {playing.value ? : }
-
-
-
-
-
-
- {progress.value &&
- videoRef.current?.duration &&
- formatTime(progress.value)}
- /
- {progress.value &&
- videoRef.current?.duration &&
- formatTime(videoRef.current?.duration)}
-
-
- {isMuted.value ? : }
-
-
- increaseSpeed()}
- >
- Speed: {playbackRate}x
-
+
+ {videoRef.current?.duration ? formatTime(progress.value) : ""}
+ {" / "}
+ {videoRef.current?.duration
+ ? formatTime(videoRef.current?.duration)
+ : ""}
+
+
+ );
+};
+export const VolumeButton = () => {
+ const { isMuted, toggleMute } = useVideoContext();
+ return (
+
+
+ {isMuted.value ? : }
+
+
+ );
+};
+
+export const VolumeSlider = () => {
+ const { volume, onVolumeChange } = useVideoContext();
+ return (
+
+ );
+};
+
+export const PlaybackRate = () => {
+ const { playbackRate, increaseSpeed, isScreenSmall } = useVideoContext();
+ return (
+
+ increaseSpeed()}
+ >
+
+ {playbackRate}x
+
+
+
+ );
+};
+
+export const PictureInPictureButton = () => {
+ const { isFullscreen, toggleRef, togglePictureInPicture } = useVideoContext();
+ return (
+ <>
+ {!isFullscreen.value && (
+
-
-
-
- >
- ) : null}
-
+
+ )}
+ >
+ );
+};
+
+export const FullscreenButton = () => {
+ const { toggleFullscreen } = useVideoContext();
+ return (
+
+ toggleFullscreen()}
+ >
+
+
+
);
};
diff --git a/src/components/common/VideoPlayer/Components/VideoControlsBar.tsx b/src/components/common/VideoPlayer/Components/VideoControlsBar.tsx
new file mode 100644
index 0000000..c0d7f2d
--- /dev/null
+++ b/src/components/common/VideoPlayer/Components/VideoControlsBar.tsx
@@ -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 (
+
+ {showMobileControls ? (
+
+ ) : canPlay.value ? (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ ) : null}
+
+ );
+};
diff --git a/src/components/common/VideoPlayer/VideoPlayer-State.ts b/src/components/common/VideoPlayer/VideoPlayer-State.ts
index 8ffcfaa..f688d04 100644
--- a/src/components/common/VideoPlayer/VideoPlayer-State.ts
+++ b/src/components/common/VideoPlayer/VideoPlayer-State.ts
@@ -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,
};
};
diff --git a/src/components/common/VideoPlayer/VideoPlayer-styles.ts b/src/components/common/VideoPlayer/VideoPlayer-styles.ts
index 87c56d8..84bab4b 100644
--- a/src/components/common/VideoPlayer/VideoPlayer-styles.ts
+++ b/src/components/common/VideoPlayer/VideoPlayer-styles.ts
@@ -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);
`;
diff --git a/src/components/common/VideoPlayer/VideoPlayer.tsx b/src/components/common/VideoPlayer/VideoPlayer.tsx
index 11d42cc..aa133d5 100644
--- a/src/components/common/VideoPlayer/VideoPlayer.tsx
+++ b/src/components/common/VideoPlayer/VideoPlayer.tsx
@@ -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(
const contextData = useContextData(props, ref);
const {
- keyboardShortcutsUp,
- keyboardShortcutsDown,
+ keyboardShortcuts,
from,
videoStyles,
containerRef,
@@ -55,18 +55,35 @@ export const VideoPlayer = forwardRef(
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 (
{
+ showControlsFullScreen.value = true;
+ }}
+ onMouseLeave={e => {
+ showControlsFullScreen.value = false;
+ }}
ref={containerRef}
>
@@ -83,23 +100,17 @@ export const VideoPlayer = forwardRef(
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%",
+ }}
/>
-
+ {showControls && }
);
diff --git a/src/constants/Misc.ts b/src/constants/Misc.ts
index d581dd9..108d72e 100644
--- a/src/constants/Misc.ts
+++ b/src/constants/Misc.ts
@@ -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";
diff --git a/src/hooks/useIdleTimeout.ts b/src/hooks/useIdleTimeout.ts
new file mode 100644
index 0000000..b5b893e
--- /dev/null
+++ b/src/hooks/useIdleTimeout.ts
@@ -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;
diff --git a/src/pages/ContentPages/VideoContent/VideoContent.tsx b/src/pages/ContentPages/VideoContent/VideoContent.tsx
index e2ac061..9cd4cf2 100644
--- a/src/pages/ContentPages/VideoContent/VideoContent.tsx
+++ b/src/pages/ContentPages/VideoContent/VideoContent.tsx
@@ -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(
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 = () => {
) : (
)}
-
+
) => {
+ if (!fontSize) fontSize = "160%";
+ const text = {title};
+
+ // put controls into individual components
+ return (
+
+ {children}
+
+ );
+};
diff --git a/vite.config.ts b/vite.config.ts
index 5c33a21..9d7d20b 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -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: "",
+});