From ee96d52f0e8fd1ca097329e9b58a7115af620677 Mon Sep 17 00:00:00 2001 From: IrohDW Date: Sat, 16 Nov 2024 07:57:30 -0700 Subject: [PATCH] Fixed bug that starts videos at normal speed instead of saved playbackRate. Removed unnecessary useSignals() hook from components. StatsData.tsx uses Signals instead of Redux. New videos or edited videos that change the source file now display how long the video is and its file size. --- src/{App-State.ts => App-Functions.ts} | 16 +- src/App.tsx | 14 +- .../Publish/EditVideo/EditVideo.tsx | 25 ++- .../Publish/PublishVideo/PublishVideo.tsx | 24 ++- src/components/StatsData.tsx | 51 +++--- .../common/ContentButtons/SubscribeButton.tsx | 2 +- .../common/FrameExtractor/FrameExtractor.tsx | 147 ++++++++++-------- .../VideoPlayer/Components/LoadingVideo.tsx | 72 +++++---- .../Components/VideoControls-State.ts | 38 ++--- .../VideoPlayer/Components/VideoControls.tsx | 13 +- .../common/VideoPlayer/VideoPlayer.tsx | 4 +- .../common/VideoPlayer/VideoPlayerGlobal.tsx | 26 +--- src/hooks/useFetchVideos.tsx | 26 ++-- .../VideoContent/VideoContent-styles.tsx | 3 +- .../VideoContent/VideoContent.tsx | 17 +- src/pages/Home/Components/VideoList.tsx | 18 ++- src/state/features/globalSlice.ts | 69 +++----- src/utils/numberFunctions.ts | 44 +++++- 18 files changed, 320 insertions(+), 289 deletions(-) rename src/{App-State.ts => App-Functions.ts} (72%) diff --git a/src/App-State.ts b/src/App-Functions.ts similarity index 72% rename from src/App-State.ts rename to src/App-Functions.ts index 685e80b..cc50133 100644 --- a/src/App-State.ts +++ b/src/App-Functions.ts @@ -1,20 +1,5 @@ -import { useEffect, useState } from "react"; import { SubscriptionData } from "./components/common/ContentButtons/SubscribeButton.tsx"; -import { setFilteredSubscriptions } from "./state/features/videoSlice.ts"; import { store } from "./state/store.ts"; -import { persistStore } from "redux-persist"; - -export const useAppState = () => { - const [theme, setTheme] = useState("dark"); - const persistor = persistStore(store); - - useEffect(() => { - subscriptionListFilter(false).then(filteredList => { - store.dispatch(setFilteredSubscriptions(filteredList)); - }); - }, []); - return { persistor, theme, setTheme }; -}; export const getUserName = async () => { const account = await qortalRequest({ @@ -28,6 +13,7 @@ export const getUserName = async () => { if (nameData?.length > 0) return nameData[0].name; else return ""; }; + export const filterVideosByName = ( subscriptionList: SubscriptionData[], userName: string diff --git a/src/App.tsx b/src/App.tsx index aac7ec1..db7fc08 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,15 +1,18 @@ import { CssBaseline } from "@mui/material"; import { ThemeProvider } from "@mui/material/styles"; +import { useEffect, useState } from "react"; import { Provider } from "react-redux"; import { Route, Routes } from "react-router-dom"; +import { persistStore } from "redux-persist"; import { PersistGate } from "redux-persist/integration/react"; -import { useAppState } from "./App-State.ts"; +import { subscriptionListFilter } from "./App-Functions.ts"; import Notification from "./components/common/Notification/Notification"; import { useIframe } from "./hooks/useIframe.tsx"; import { IndividualProfile } from "./pages/ContentPages/IndividualProfile/IndividualProfile"; import { PlaylistContent } from "./pages/ContentPages/PlaylistContent/PlaylistContent"; import { VideoContent } from "./pages/ContentPages/VideoContent/VideoContent"; import { Home } from "./pages/Home/Home"; +import { setFilteredSubscriptions } from "./state/features/videoSlice.ts"; import { store } from "./state/store"; import { darkTheme, lightTheme } from "./styles/theme"; import DownloadWrapper from "./wrappers/DownloadWrapper"; @@ -18,9 +21,16 @@ import { ScrollWrapper } from "./wrappers/ScrollWrapper.tsx"; function App() { // const themeColor = window._qdnTheme - const { persistor, theme, setTheme } = useAppState(); + const persistor = persistStore(store); + const [theme, setTheme] = useState("dark"); useIframe(); + useEffect(() => { + subscriptionListFilter(false).then(filteredList => { + store.dispatch(setFilteredSubscriptions(filteredList)); + }); + }, []); + return ( diff --git a/src/components/Publish/EditVideo/EditVideo.tsx b/src/components/Publish/EditVideo/EditVideo.tsx index 05688d6..bb97d0d 100644 --- a/src/components/Publish/EditVideo/EditVideo.tsx +++ b/src/components/Publish/EditVideo/EditVideo.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from "react"; import Compressor from "compressorjs"; +import { formatBytes } from "../../../utils/numberFunctions.ts"; import { AddCoverImageButton, @@ -62,25 +63,11 @@ import { titleFormatter, videoMaxSize, } from "../../../constants/Misc.ts"; +import { Signal, useSignal } from "@preact/signals-react"; const uid = new ShortUniqueId(); const shortuid = new ShortUniqueId({ length: 5 }); -interface NewCrowdfundProps { - editId?: string; - editContent?: null | { - title: string; - user: string; - coverImage: string | null; - }; -} - -interface VideoFile { - file: File; - title: string; - description: string; - coverImage?: string; -} export const EditVideo = () => { const theme = useTheme(); const dispatch = useDispatch(); @@ -106,6 +93,10 @@ export const EditVideo = () => { useState(null); const [imageExtracts, setImageExtracts] = useState([]); + const videoDuration: Signal = useSignal([ + editVideoProperties?.duration || 0, + ]); + const { getRootProps, getInputProps } = useDropzone({ accept: { "video/*": [], @@ -293,6 +284,8 @@ export const EditVideo = () => { code: editVideoProperties.code, videoType: file?.type || "video/mp4", filename: `${alphanumericString.trim()}.${fileExtension}`, + fileSize: file?.size || 0, + duration: videoDuration.value[0], }; const metadescription = @@ -508,6 +501,8 @@ export const EditVideo = () => { onFramesExtracted(imgs)} + videoDurations={videoDuration} + index={0} /> )} diff --git a/src/components/Publish/PublishVideo/PublishVideo.tsx b/src/components/Publish/PublishVideo/PublishVideo.tsx index 1a3cda3..a62642f 100644 --- a/src/components/Publish/PublishVideo/PublishVideo.tsx +++ b/src/components/Publish/PublishVideo/PublishVideo.tsx @@ -16,7 +16,7 @@ import { useTheme, } from "@mui/material"; import Compressor from "compressorjs"; -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { useDropzone } from "react-dropzone"; import { useDispatch, useSelector } from "react-redux"; import ShortUniqueId from "short-unique-id"; @@ -38,6 +38,7 @@ import { import { setNotification } from "../../../state/features/notificationsSlice.ts"; import { RootState } from "../../../state/store.ts"; +import { formatBytes } from "../../../utils/numberFunctions.ts"; import { objectToBase64 } from "../../../utils/PublishFormatter.ts"; import { getFileName } from "../../../utils/stringFunctions.ts"; import { CardContentContainerComment } from "../../common/Comments/Comments-styles.tsx"; @@ -65,6 +66,8 @@ import { StyledButton, TimesIcon, } from "./PublishVideo-styles.tsx"; +import { signal, Signal, useSignal } from "@preact/signals-react"; +import { useSignals } from "@preact/signals-react/runtime"; export const toBase64 = (file: File): Promise => new Promise((resolve, reject) => { @@ -103,10 +106,8 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => { (state: RootState) => state.auth?.user?.address ); const [files, setFiles] = useState([]); - + const videoDurations = useSignal([]); const [isOpen, setIsOpen] = useState(false); - const [title, setTitle] = useState(""); - const [description, setDescription] = useState(""); const [coverImageForAll, setCoverImageForAll] = useState(""); const [step, setStep] = useState("videos"); @@ -136,6 +137,14 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => { useState(false); const [imageExtracts, setImageExtracts] = useState({}); + useSignals(); + const assembleVideoDurations = () => { + if (files.length === videoDurations.value.length) return; + const newArray: number[] = []; + files.map(() => newArray.push(0)); + videoDurations.value = [...newArray]; + }; + const { getRootProps, getInputProps } = useDropzone({ accept: { "video/*": [], @@ -302,6 +311,8 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => { code, videoType: file?.type || "video/mp4", filename: `${alphanumericString.trim()}.${fileExtension}`, + fileSize: file?.size || 0, + duration: videoDurations.value[i], }; const metadescription = @@ -818,11 +829,14 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => { )} {files.map((file, index) => { + assembleVideoDurations(); return ( onFramesExtracted(imgs, index)} + videoDurations={videoDurations} + index={index} /> {file?.file?.name} {!isCheckSameCoverImage && ( @@ -1159,7 +1173,7 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => { { + setInlineContent={(value: string) => { setPlaylistDescription(value); }} /> diff --git a/src/components/StatsData.tsx b/src/components/StatsData.tsx index a474ca3..10d489a 100644 --- a/src/components/StatsData.tsx +++ b/src/components/StatsData.tsx @@ -4,6 +4,12 @@ import { Grid } from "@mui/material"; import { useFetchVideos } from "../hooks/useFetchVideos.tsx"; import { useSelector } from "react-redux"; import { RootState } from "../state/store.ts"; +import { signal } from "@preact/signals-react"; + +/* eslint-disable react-refresh/only-export-components */ +export const totalVideosPublished = signal(0); +export const totalNamesPublished = signal(0); +export const videosPerNamePublished = signal(0); export const StatsData = () => { const StatsCol = styled(Grid)(({ theme }) => ({ @@ -14,44 +20,43 @@ export const StatsData = () => { backgroundColor: theme.palette.background.default, })); - const { - getVideos, - getNewVideos, - checkNewVideos, - getVideosFiltered, - getVideosCount, - } = useFetchVideos(); + const { getVideosCount } = useFetchVideos(); - const persistReducer = useSelector((state: RootState) => state.persist); - const totalVideosPublished = useSelector( - (state: RootState) => state.global.totalVideosPublished - ); - const totalNamesPublished = useSelector( - (state: RootState) => state.global.totalNamesPublished - ); - const videosPerNamePublished = useSelector( - (state: RootState) => state.global.videosPerNamePublished - ); + const showValueIfExists = (value: number) => { + return value > 0 ? "inline" : "none"; + }; + const showStats = useSelector((state: RootState) => state.persist.showStats); + const showVideoCount = showValueIfExists(totalVideosPublished.value); + const showPublisherCount = showValueIfExists(totalNamesPublished.value); + const showAverage = showValueIfExists(videosPerNamePublished.value); useEffect(() => { getVideosCount(); }, [getVideosCount]); return ( - +
Videos:{" "} - {totalVideosPublished} + + {totalVideosPublished.value} +
Publishers:{" "} - {totalNamesPublished} + + {totalNamesPublished.value} +
Average:{" "} - - {videosPerNamePublished > 0 && - Number(videosPerNamePublished).toFixed(0)} + + {Number(videosPerNamePublished.value).toFixed(0)}
diff --git a/src/components/common/ContentButtons/SubscribeButton.tsx b/src/components/common/ContentButtons/SubscribeButton.tsx index 14a3cba..136c942 100644 --- a/src/components/common/ContentButtons/SubscribeButton.tsx +++ b/src/components/common/ContentButtons/SubscribeButton.tsx @@ -1,7 +1,7 @@ import { Button, ButtonProps, Tooltip } from "@mui/material"; import { MouseEvent, useEffect, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { subscriptionListFilter } from "../../../App-State.ts"; +import { subscriptionListFilter } from "../../../App-Functions.ts"; import { RootState } from "../../../state/store.ts"; import { subscribe, diff --git a/src/components/common/FrameExtractor/FrameExtractor.tsx b/src/components/common/FrameExtractor/FrameExtractor.tsx index 04d47fa..57116ad 100644 --- a/src/components/common/FrameExtractor/FrameExtractor.tsx +++ b/src/components/common/FrameExtractor/FrameExtractor.tsx @@ -1,77 +1,92 @@ -import React, { useEffect, useRef, useState, useMemo } from 'react'; +import { Signal } from "@preact/signals-react"; +import { useSignals } from "@preact/signals-react/runtime"; +import React, { useEffect, useRef, useState, useMemo } from "react"; -export const FrameExtractor = ({ videoFile, onFramesExtracted }) => { - const videoRef = useRef(null); - const [durations, setDurations] = useState([]); - const canvasRef = useRef(null); +export interface FrameExtractorProps { + videoFile: File; + onFramesExtracted: (imgs, index?: number) => Promise; + videoDurations?: Signal; + index?: number; +} - useEffect(() => { - const video = videoRef.current; - video.addEventListener('loadedmetadata', () => { - const duration = video.duration; - if (isFinite(duration)) { - // Proceed with your logic - - console.log('duration', duration) - const section = duration / 4; - let timestamps = []; +export const FrameExtractor = ({ + videoFile, + onFramesExtracted, + videoDurations, + index, +}: FrameExtractorProps) => { + useSignals(); + const videoRef = useRef(null); + const [durations, setDurations] = useState([]); + const canvasRef = useRef(null); - for (let i = 0; i < 4; i++) { - const randomTime = Math.random() * section + i * section; - timestamps.push(randomTime); - } + useEffect(() => { + const video = videoRef.current; + video.addEventListener("loadedmetadata", () => { + const duration = video.duration; + if (isFinite(duration)) { + // Proceed with your logic - setDurations(timestamps); - } else { - onFramesExtracted([]) + const newVideoDurations = [...videoDurations.value]; + newVideoDurations[index] = duration; + videoDurations.value = [...newVideoDurations]; + + const section = duration / 4; + const timestamps = []; + + for (let i = 0; i < 4; i++) { + const randomTime = Math.random() * section + i * section; + timestamps.push(randomTime); } - }); - }, [videoFile]); - console.log({durations}) + setDurations(timestamps); + } else { + onFramesExtracted([]); + } + }); + }, [videoFile]); - useEffect(() => { - if (durations.length === 4) { - extractFrames(); - } - }, [durations]); + useEffect(() => { + if (durations.length === 4) { + extractFrames(); + } + }, [durations]); - const fileUrl = useMemo(() => { - return URL.createObjectURL(videoFile); - }, [videoFile]); + const fileUrl = useMemo(() => { + return URL.createObjectURL(videoFile); + }, [videoFile]); - const extractFrames = async () => { - const video = videoRef.current; - const canvas = canvasRef.current; - canvas.width = video.videoWidth; - canvas.height = video.videoHeight; - const context = canvas.getContext('2d'); - - let frameData = []; - - for (const time of durations) { - await new Promise((resolve) => { - video.currentTime = time; - const onSeeked = () => { - context.drawImage(video, 0, 0, canvas.width, canvas.height); - canvas.toBlob(blob => { - frameData.push(blob); - resolve(); - }, 'image/png'); - video.removeEventListener('seeked', onSeeked); - }; - video.addEventListener('seeked', onSeeked, { once: true }); - }); - } - - onFramesExtracted(frameData); - }; - + const extractFrames = async () => { + const video = videoRef.current; + const canvas = canvasRef.current; + canvas.width = video.videoWidth; + canvas.height = video.videoHeight; + const context = canvas.getContext("2d"); - return ( -
- - -
- ); + const frameData = []; + + for (const time of durations) { + await new Promise(resolve => { + video.currentTime = time; + const onSeeked = () => { + context.drawImage(video, 0, 0, canvas.width, canvas.height); + canvas.toBlob(blob => { + frameData.push(blob); + resolve(); + }, "image/png"); + video.removeEventListener("seeked", onSeeked); + }; + video.addEventListener("seeked", onSeeked, { once: true }); + }); + } + + onFramesExtracted(frameData); + }; + + return ( +
+ + +
+ ); }; diff --git a/src/components/common/VideoPlayer/Components/LoadingVideo.tsx b/src/components/common/VideoPlayer/Components/LoadingVideo.tsx index 131c1d4..3d75451 100644 --- a/src/components/common/VideoPlayer/Components/LoadingVideo.tsx +++ b/src/components/common/VideoPlayer/Components/LoadingVideo.tsx @@ -2,6 +2,7 @@ import { Box, CircularProgress, Typography } from "@mui/material"; import { setVideoPlaying } from "../../../../state/features/globalSlice.ts"; import { useDispatch } from "react-redux"; import { PlayArrow } from "@mui/icons-material"; +import { formatTime } from "../../../../utils/numberFunctions.ts"; import { useVideoContext } from "./VideoContext.ts"; export const LoadingVideo = () => { @@ -13,6 +14,7 @@ export const LoadingVideo = () => { canPlay, from, togglePlay, + duration, } = useVideoContext(); const getDownloadProgress = (current: number, total: number) => { @@ -83,36 +85,50 @@ export const LoadingVideo = () => { )} )} - {((!src && !isLoading.value) || (!startPlay.value && !canPlay.value)) && ( - { - if (from === "create") return; - dispatch(setVideoPlaying(null)); - togglePlay(); - }} - sx={{ - cursor: "pointer", - }} - > - + {duration && ( + + {formatTime(duration)} + + )} + { + if (from === "create") return; + dispatch(setVideoPlaying(null)); + + togglePlay(); }} - /> - + sx={{ + cursor: "pointer", + }} + > + + + )} ); diff --git a/src/components/common/VideoPlayer/Components/VideoControls-State.ts b/src/components/common/VideoPlayer/Components/VideoControls-State.ts index 2368473..050ab0c 100644 --- a/src/components/common/VideoPlayer/Components/VideoControls-State.ts +++ b/src/components/common/VideoPlayer/Components/VideoControls-State.ts @@ -1,5 +1,5 @@ import { useSignal } from "@preact/signals-react"; -import { useSignals } from "@preact/signals-react/runtime"; +import { useSignalEffect, useSignals } from "@preact/signals-react/runtime"; import { useEffect } from "react"; import ReactDOM from "react-dom"; @@ -35,6 +35,7 @@ export const useVideoControlsState = ( progress, videoObjectFit, canPlay, + isMobileView, } = videoPlayerState; const { identifier, autoPlay } = props; @@ -114,31 +115,6 @@ export const useVideoControlsState = ( }; }, []); - function formatTime(seconds: number): string { - seconds = Math.floor(seconds); - const minutes: number | string = Math.floor(seconds / 60); - let hours: number | string = Math.floor(minutes / 60); - - let remainingSeconds: number | string = seconds % 60; - let remainingMinutes: number | string = minutes % 60; - - if (remainingSeconds < 10) { - remainingSeconds = "0" + remainingSeconds; - } - - if (remainingMinutes < 10) { - remainingMinutes = "0" + remainingMinutes; - } - - if (hours === 0) { - hours = ""; - } else { - hours = hours + ":"; - } - - return hours + remainingMinutes + ":" + remainingSeconds; - } - const reloadVideo = async () => { if (!videoRef.current || !src) return; const currentTime = videoRef.current.currentTime; @@ -154,6 +130,7 @@ export const useVideoControlsState = ( if (firstPlay.value) setPlaying(true); // makes the video play when fully loaded firstPlay.value = false; isLoading.value = false; + updatePlaybackRate(persistSelector.playbackRate); }; const setPlaying = async (setPlay: boolean) => { @@ -380,6 +357,14 @@ export const useVideoControlsState = ( } }, [videoPlaying, identifier, src]); + useSignalEffect(() => { + console.log("canPlay is: ", canPlay.value); // makes the function execute when canPlay changes + const videoWidth = videoRef?.current?.offsetWidth; + if (videoWidth && videoWidth <= 600) { + isMobileView.value = true; + } + }); + return { reloadVideo, togglePlay, @@ -387,7 +372,6 @@ export const useVideoControlsState = ( increaseSpeed, togglePictureInPicture, toggleFullscreen, - formatTime, keyboardShortcutsUp, keyboardShortcutsDown, handleCanPlay, diff --git a/src/components/common/VideoPlayer/Components/VideoControls.tsx b/src/components/common/VideoPlayer/Components/VideoControls.tsx index 15cd00a..03e3283 100644 --- a/src/components/common/VideoPlayer/Components/VideoControls.tsx +++ b/src/components/common/VideoPlayer/Components/VideoControls.tsx @@ -8,15 +8,13 @@ import { VolumeUp, } from "@mui/icons-material"; import { IconButton, Slider, Typography } from "@mui/material"; -import { useSignalEffect, useSignals } from "@preact/signals-react/runtime"; -import { useEffect } from "react"; +import { formatTime } from "../../../../utils/numberFunctions.ts"; import { ControlsContainer } from "../VideoPlayer-styles.ts"; import { MobileControls } from "./MobileControls.tsx"; import { useVideoContext } from "./VideoContext.ts"; export const VideoControls = () => { - useSignals(); const { reloadVideo, togglePlay, @@ -24,7 +22,6 @@ export const VideoControls = () => { increaseSpeed, togglePictureInPicture, toggleFullscreen, - formatTime, toggleMute, onProgressChange, toggleRef, @@ -40,14 +37,6 @@ export const VideoControls = () => { showControlsFullScreen, } = useVideoContext(); - useSignalEffect(() => { - console.log("canPlay is: ", canPlay.value); // makes the function execute when canPlay changes - const videoWidth = videoRef?.current?.offsetWidth; - if (videoWidth && videoWidth <= 600) { - isMobileView.value = true; - } - }); - const showMobileControls = isMobileView.value && canPlay.value && showControlsFullScreen.value; diff --git a/src/components/common/VideoPlayer/VideoPlayer.tsx b/src/components/common/VideoPlayer/VideoPlayer.tsx index 7e6f8b3..3b81d5a 100644 --- a/src/components/common/VideoPlayer/VideoPlayer.tsx +++ b/src/components/common/VideoPlayer/VideoPlayer.tsx @@ -1,4 +1,3 @@ -import { useSignals } from "@preact/signals-react/runtime"; import CSS from "csstype"; import { forwardRef } from "react"; import { LoadingVideo } from "./Components/LoadingVideo.tsx"; @@ -26,6 +25,7 @@ export interface VideoPlayerProps { onEnd?: () => void; autoPlay?: boolean; style?: CSS.Properties; + duration?: number; } export type videoRefType = { @@ -34,7 +34,6 @@ export type videoRefType = { }; export const VideoPlayer = forwardRef( (props: VideoPlayerProps, ref) => { - useSignals(); const contextData = useContextData(props, ref); const { @@ -56,6 +55,7 @@ export const VideoPlayer = forwardRef( startPlay, videoObjectFit, showControlsFullScreen, + duration, } = contextData; return ( diff --git a/src/components/common/VideoPlayer/VideoPlayerGlobal.tsx b/src/components/common/VideoPlayer/VideoPlayerGlobal.tsx index 360beb7..e2cb0dd 100644 --- a/src/components/common/VideoPlayer/VideoPlayerGlobal.tsx +++ b/src/components/common/VideoPlayer/VideoPlayerGlobal.tsx @@ -12,6 +12,7 @@ import { VolumeOff, } from "@mui/icons-material"; import { styled } from "@mui/system"; +import { formatTime } from "../../../utils/numberFunctions.ts"; import { MyContext } from "../../../wrappers/DownloadWrapper.tsx"; import { useDispatch, useSelector } from "react-redux"; import { RootState } from "../../../state/store.ts"; @@ -250,31 +251,6 @@ export const VideoPlayerGlobal: React.FC = ({ }; }, []); - function formatTime(seconds: number): string { - seconds = Math.floor(seconds); - const minutes: number | string = Math.floor(seconds / 60); - let hours: number | string = Math.floor(minutes / 60); - - let remainingSeconds: number | string = seconds % 60; - let remainingMinutes: number | string = minutes % 60; - - if (remainingSeconds < 10) { - remainingSeconds = "0" + remainingSeconds; - } - - if (remainingMinutes < 10) { - remainingMinutes = "0" + remainingMinutes; - } - - if (hours === 0) { - hours = ""; - } else { - hours = hours + ":"; - } - - return hours + remainingMinutes + ":" + remainingSeconds; - } - const reloadVideo = async () => { if (!videoRef.current) return; const src = videoRef.current.src; diff --git a/src/hooks/useFetchVideos.tsx b/src/hooks/useFetchVideos.tsx index 1355101..331d83f 100644 --- a/src/hooks/useFetchVideos.tsx +++ b/src/hooks/useFetchVideos.tsx @@ -1,6 +1,11 @@ import React from "react"; import { useDispatch, useSelector } from "react-redux"; -import { subscriptionListFilter } from "../App-State.ts"; +import { subscriptionListFilter } from "../App-Functions.ts"; +import { + totalNamesPublished, + totalVideosPublished, + videosPerNamePublished, +} from "../components/StatsData.tsx"; import { addVideos, addToHashMap, @@ -11,13 +16,7 @@ import { upsertFilteredVideos, removeFromHashMap, } from "../state/features/videoSlice"; -import { - setIsLoadingGlobal, - setUserAvatarHash, - setTotalVideosPublished, - setTotalNamesPublished, - setVideosPerNamePublished, -} from "../state/features/globalSlice"; +import { setIsLoadingGlobal } from "../state/features/globalSlice"; import { RootState } from "../state/store"; import { fetchAndEvaluateVideos } from "../utils/fetchVideos"; import { RequestQueue } from "../utils/queue"; @@ -390,14 +389,11 @@ export const useFetchVideos = () => { }); const responseData = await response.json(); - const totalVideosPublished = responseData.length; + totalVideosPublished.value = responseData.length; const uniqueNames = new Set(responseData.map(video => video.name)); - const totalNamesPublished = uniqueNames.size; - const videosPerNamePublished = totalVideosPublished / totalNamesPublished; - - dispatch(setTotalVideosPublished(totalVideosPublished)); - dispatch(setTotalNamesPublished(totalNamesPublished)); - dispatch(setVideosPerNamePublished(videosPerNamePublished)); + totalNamesPublished.value = uniqueNames.size; + videosPerNamePublished.value = + totalVideosPublished.value / totalNamesPublished.value; } catch (error) { console.log({ error }); } diff --git a/src/pages/ContentPages/VideoContent/VideoContent-styles.tsx b/src/pages/ContentPages/VideoContent/VideoContent-styles.tsx index bff7089..9fda806 100644 --- a/src/pages/ContentPages/VideoContent/VideoContent-styles.tsx +++ b/src/pages/ContentPages/VideoContent/VideoContent-styles.tsx @@ -1,5 +1,6 @@ import { styled } from "@mui/system"; import { Box, Grid, Typography, Checkbox } from "@mui/material"; +import { fontSizeMedium } from "../../../constants/Misc.ts"; export const VideoContentContainer = styled(Box)(({ theme }) => ({ display: "flex", @@ -14,7 +15,7 @@ export const VideoPlayerContainer = styled(Box)(({ theme }) => ({ export const VideoTitle = styled(Typography)(({ theme }) => ({ fontFamily: "Raleway", - fontSize: "20px", + fontSize: fontSizeMedium, color: theme.palette.text.primary, wordBreak: "break-word", })); diff --git a/src/pages/ContentPages/VideoContent/VideoContent.tsx b/src/pages/ContentPages/VideoContent/VideoContent.tsx index 810b05e..fba7859 100644 --- a/src/pages/ContentPages/VideoContent/VideoContent.tsx +++ b/src/pages/ContentPages/VideoContent/VideoContent.tsx @@ -14,6 +14,7 @@ import { SUPER_LIKE_BASE, } from "../../../constants/Identifiers.ts"; import { + fontSizeMedium, minPriceSuperlike, titleFormatterOnSave, } from "../../../constants/Misc.ts"; @@ -21,6 +22,7 @@ import { useFetchSuperLikes } from "../../../hooks/useFetchSuperLikes.tsx"; import { setIsLoadingGlobal } from "../../../state/features/globalSlice.ts"; import { addToHashMap } from "../../../state/features/videoSlice.ts"; import { RootState } from "../../../state/store.ts"; +import { formatBytes } from "../../../utils/numberFunctions.ts"; import { formatDate } from "../../../utils/time.ts"; import { VideoActionsBar } from "./VideoActionsBar.tsx"; import { @@ -62,7 +64,6 @@ export const VideoContent = () => { superLikeList, setSuperLikeList, } = useVideoContentState(); - return ( <> { videoContainer: { aspectRatio: "16 / 9" }, video: { aspectRatio: "16 / 9" }, }} + duration={videoData?.duration} /> ) : isVideoLoaded ? ( @@ -128,7 +130,17 @@ export const VideoContent = () => { {videoData?.title} - + {videoData?.fileSize && ( + + {formatBytes(videoData.fileSize, 2, "Decimal")} + + )} {videoData?.created && ( { {formatDate(videoData.created)} )} - {videoData?.fullDescription && ( { videoObj = existingVideo; hasHash = true; } - // nb. this prevents showing metadata for a video which // belongs to a different user if ( @@ -147,7 +147,6 @@ export const VideoList = ({ videos }: VideoListProps) => { /> {videoObj?.title} - { @@ -237,6 +236,19 @@ export const VideoList = ({ videos }: VideoListProps) => { navigate(`/video/${videoObj?.user}/${videoObj?.id}`); }} > + {videoObj?.duration && ( + + + {formatTime(videoObj.duration)} + + + )} - publishNames: string[] | null - videoPlaying: any | null - superlikelistAll: any[] - totalVideosPublished: number - totalNamesPublished: number - videosPerNamePublished: number + isLoadingGlobal: boolean; + downloads: any; + userAvatarHash: Record; + publishNames: string[] | null; + videoPlaying: any | null; + superlikelistAll: any[]; } const initialState: GlobalState = { isLoadingGlobal: false, @@ -19,56 +15,44 @@ const initialState: GlobalState = { publishNames: null, videoPlaying: null, superlikelistAll: [], - totalVideosPublished: null, - totalNamesPublished: null, - videosPerNamePublished: null -} +}; export const globalSlice = createSlice({ - name: 'global', + name: "global", initialState, reducers: { setIsLoadingGlobal: (state, action) => { - state.isLoadingGlobal = action.payload + state.isLoadingGlobal = action.payload; }, setAddToDownloads: (state, action) => { - const download = action.payload - state.downloads[download.identifier] = download + const download = action.payload; + state.downloads[download.identifier] = download; }, updateDownloads: (state, action) => { - const { identifier } = action.payload - const download = action.payload + const { identifier } = action.payload; + const download = action.payload; state.downloads[identifier] = { ...state.downloads[identifier], - ...download - } + ...download, + }; }, setUserAvatarHash: (state, action) => { - const avatar = action.payload + const avatar = action.payload; if (avatar?.name && avatar?.url) { - state.userAvatarHash[avatar?.name] = avatar?.url + state.userAvatarHash[avatar?.name] = avatar?.url; } }, addPublishNames: (state, action) => { - state.publishNames = action.payload + state.publishNames = action.payload; }, setVideoPlaying: (state, action) => { - state.videoPlaying = action.payload + state.videoPlaying = action.payload; }, setSuperlikesAll: (state, action) => { - state.superlikelistAll = action.payload + state.superlikelistAll = action.payload; }, - setTotalVideosPublished: (state, action) => { - state.totalVideosPublished = action.payload - }, - setTotalNamesPublished: (state, action) => { - state.totalNamesPublished = action.payload - }, - setVideosPerNamePublished: (state, action) => { - state.videosPerNamePublished = action.payload - }, - } -}) + }, +}); export const { setIsLoadingGlobal, @@ -78,9 +62,6 @@ export const { addPublishNames, setVideoPlaying, setSuperlikesAll, - setTotalVideosPublished, - setTotalNamesPublished, - setVideosPerNamePublished -} = globalSlice.actions +} = globalSlice.actions; -export default globalSlice.reducer +export default globalSlice.reducer; diff --git a/src/utils/numberFunctions.ts b/src/utils/numberFunctions.ts index d6247f3..583196c 100644 --- a/src/utils/numberFunctions.ts +++ b/src/utils/numberFunctions.ts @@ -1,5 +1,3 @@ - - export const truncateNumber = (value: string | number, sigDigits: number) => { return Number(value).toFixed(sigDigits); }; @@ -21,3 +19,45 @@ export const setNumberWithinBounds = ( export const numberToInt = (num: number) => { return Math.floor(num); }; + +type ByteFormat = "Decimal" | "Binary"; +export function formatBytes( + bytes: number, + decimals = 2, + format: ByteFormat = "Binary" +) { + if (bytes === 0) return "0 Bytes"; + + const k = format === "Binary" ? 1024 : 1000; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i]; +} + +export function formatTime(seconds: number): string { + seconds = Math.floor(seconds); + const minutes: number | string = Math.floor(seconds / 60); + let hours: number | string = Math.floor(minutes / 60); + + let remainingSeconds: number | string = seconds % 60; + let remainingMinutes: number | string = minutes % 60; + + if (remainingSeconds < 10) { + remainingSeconds = "0" + remainingSeconds; + } + + if (remainingMinutes < 10) { + remainingMinutes = "0" + remainingMinutes; + } + + if (hours === 0) { + hours = ""; + } else { + hours = hours + ":"; + } + + return hours + remainingMinutes + ":" + remainingSeconds; +}