mirror of
https://github.com/Qortal/q-tube.git
synced 2025-02-16 04:05:52 +00:00
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.
This commit is contained in:
parent
9614b1c132
commit
ee96d52f0e
@ -1,20 +1,5 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import { SubscriptionData } from "./components/common/ContentButtons/SubscribeButton.tsx";
|
import { SubscriptionData } from "./components/common/ContentButtons/SubscribeButton.tsx";
|
||||||
import { setFilteredSubscriptions } from "./state/features/videoSlice.ts";
|
|
||||||
import { store } from "./state/store.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 () => {
|
export const getUserName = async () => {
|
||||||
const account = await qortalRequest({
|
const account = await qortalRequest({
|
||||||
@ -28,6 +13,7 @@ export const getUserName = async () => {
|
|||||||
if (nameData?.length > 0) return nameData[0].name;
|
if (nameData?.length > 0) return nameData[0].name;
|
||||||
else return "";
|
else return "";
|
||||||
};
|
};
|
||||||
|
|
||||||
export const filterVideosByName = (
|
export const filterVideosByName = (
|
||||||
subscriptionList: SubscriptionData[],
|
subscriptionList: SubscriptionData[],
|
||||||
userName: string
|
userName: string
|
14
src/App.tsx
14
src/App.tsx
@ -1,15 +1,18 @@
|
|||||||
import { CssBaseline } from "@mui/material";
|
import { CssBaseline } from "@mui/material";
|
||||||
import { ThemeProvider } from "@mui/material/styles";
|
import { ThemeProvider } from "@mui/material/styles";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
import { Route, Routes } from "react-router-dom";
|
import { Route, Routes } from "react-router-dom";
|
||||||
|
import { persistStore } from "redux-persist";
|
||||||
import { PersistGate } from "redux-persist/integration/react";
|
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 Notification from "./components/common/Notification/Notification";
|
||||||
import { useIframe } from "./hooks/useIframe.tsx";
|
import { useIframe } from "./hooks/useIframe.tsx";
|
||||||
import { IndividualProfile } from "./pages/ContentPages/IndividualProfile/IndividualProfile";
|
import { IndividualProfile } from "./pages/ContentPages/IndividualProfile/IndividualProfile";
|
||||||
import { PlaylistContent } from "./pages/ContentPages/PlaylistContent/PlaylistContent";
|
import { PlaylistContent } from "./pages/ContentPages/PlaylistContent/PlaylistContent";
|
||||||
import { VideoContent } from "./pages/ContentPages/VideoContent/VideoContent";
|
import { VideoContent } from "./pages/ContentPages/VideoContent/VideoContent";
|
||||||
import { Home } from "./pages/Home/Home";
|
import { Home } from "./pages/Home/Home";
|
||||||
|
import { setFilteredSubscriptions } from "./state/features/videoSlice.ts";
|
||||||
import { store } from "./state/store";
|
import { store } from "./state/store";
|
||||||
import { darkTheme, lightTheme } from "./styles/theme";
|
import { darkTheme, lightTheme } from "./styles/theme";
|
||||||
import DownloadWrapper from "./wrappers/DownloadWrapper";
|
import DownloadWrapper from "./wrappers/DownloadWrapper";
|
||||||
@ -18,9 +21,16 @@ import { ScrollWrapper } from "./wrappers/ScrollWrapper.tsx";
|
|||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
// const themeColor = window._qdnTheme
|
// const themeColor = window._qdnTheme
|
||||||
const { persistor, theme, setTheme } = useAppState();
|
const persistor = persistStore(store);
|
||||||
|
const [theme, setTheme] = useState("dark");
|
||||||
useIframe();
|
useIframe();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
subscriptionListFilter(false).then(filteredList => {
|
||||||
|
store.dispatch(setFilteredSubscriptions(filteredList));
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<PersistGate loading={null} persistor={persistor}>
|
<PersistGate loading={null} persistor={persistor}>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import Compressor from "compressorjs";
|
import Compressor from "compressorjs";
|
||||||
|
import { formatBytes } from "../../../utils/numberFunctions.ts";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AddCoverImageButton,
|
AddCoverImageButton,
|
||||||
@ -62,25 +63,11 @@ import {
|
|||||||
titleFormatter,
|
titleFormatter,
|
||||||
videoMaxSize,
|
videoMaxSize,
|
||||||
} from "../../../constants/Misc.ts";
|
} from "../../../constants/Misc.ts";
|
||||||
|
import { Signal, useSignal } from "@preact/signals-react";
|
||||||
|
|
||||||
const uid = new ShortUniqueId();
|
const uid = new ShortUniqueId();
|
||||||
const shortuid = new ShortUniqueId({ length: 5 });
|
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 = () => {
|
export const EditVideo = () => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -106,6 +93,10 @@ export const EditVideo = () => {
|
|||||||
useState<any>(null);
|
useState<any>(null);
|
||||||
const [imageExtracts, setImageExtracts] = useState<any>([]);
|
const [imageExtracts, setImageExtracts] = useState<any>([]);
|
||||||
|
|
||||||
|
const videoDuration: Signal<number[]> = useSignal([
|
||||||
|
editVideoProperties?.duration || 0,
|
||||||
|
]);
|
||||||
|
|
||||||
const { getRootProps, getInputProps } = useDropzone({
|
const { getRootProps, getInputProps } = useDropzone({
|
||||||
accept: {
|
accept: {
|
||||||
"video/*": [],
|
"video/*": [],
|
||||||
@ -293,6 +284,8 @@ export const EditVideo = () => {
|
|||||||
code: editVideoProperties.code,
|
code: editVideoProperties.code,
|
||||||
videoType: file?.type || "video/mp4",
|
videoType: file?.type || "video/mp4",
|
||||||
filename: `${alphanumericString.trim()}.${fileExtension}`,
|
filename: `${alphanumericString.trim()}.${fileExtension}`,
|
||||||
|
fileSize: file?.size || 0,
|
||||||
|
duration: videoDuration.value[0],
|
||||||
};
|
};
|
||||||
|
|
||||||
const metadescription =
|
const metadescription =
|
||||||
@ -508,6 +501,8 @@ export const EditVideo = () => {
|
|||||||
<FrameExtractor
|
<FrameExtractor
|
||||||
videoFile={file}
|
videoFile={file}
|
||||||
onFramesExtracted={imgs => onFramesExtracted(imgs)}
|
onFramesExtracted={imgs => onFramesExtracted(imgs)}
|
||||||
|
videoDurations={videoDuration}
|
||||||
|
index={0}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
@ -16,7 +16,7 @@ import {
|
|||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import Compressor from "compressorjs";
|
import Compressor from "compressorjs";
|
||||||
import React, { useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useDropzone } from "react-dropzone";
|
import { useDropzone } from "react-dropzone";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import ShortUniqueId from "short-unique-id";
|
import ShortUniqueId from "short-unique-id";
|
||||||
@ -38,6 +38,7 @@ import {
|
|||||||
|
|
||||||
import { setNotification } from "../../../state/features/notificationsSlice.ts";
|
import { setNotification } from "../../../state/features/notificationsSlice.ts";
|
||||||
import { RootState } from "../../../state/store.ts";
|
import { RootState } from "../../../state/store.ts";
|
||||||
|
import { formatBytes } from "../../../utils/numberFunctions.ts";
|
||||||
import { objectToBase64 } from "../../../utils/PublishFormatter.ts";
|
import { objectToBase64 } from "../../../utils/PublishFormatter.ts";
|
||||||
import { getFileName } from "../../../utils/stringFunctions.ts";
|
import { getFileName } from "../../../utils/stringFunctions.ts";
|
||||||
import { CardContentContainerComment } from "../../common/Comments/Comments-styles.tsx";
|
import { CardContentContainerComment } from "../../common/Comments/Comments-styles.tsx";
|
||||||
@ -65,6 +66,8 @@ import {
|
|||||||
StyledButton,
|
StyledButton,
|
||||||
TimesIcon,
|
TimesIcon,
|
||||||
} from "./PublishVideo-styles.tsx";
|
} 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<string | ArrayBuffer | null> =>
|
export const toBase64 = (file: File): Promise<string | ArrayBuffer | null> =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
@ -103,10 +106,8 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
(state: RootState) => state.auth?.user?.address
|
(state: RootState) => state.auth?.user?.address
|
||||||
);
|
);
|
||||||
const [files, setFiles] = useState<VideoFile[]>([]);
|
const [files, setFiles] = useState<VideoFile[]>([]);
|
||||||
|
const videoDurations = useSignal<number[]>([]);
|
||||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||||
const [title, setTitle] = useState<string>("");
|
|
||||||
const [description, setDescription] = useState<string>("");
|
|
||||||
const [coverImageForAll, setCoverImageForAll] = useState<null | string>("");
|
const [coverImageForAll, setCoverImageForAll] = useState<null | string>("");
|
||||||
|
|
||||||
const [step, setStep] = useState<string>("videos");
|
const [step, setStep] = useState<string>("videos");
|
||||||
@ -136,6 +137,14 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
useState(false);
|
useState(false);
|
||||||
const [imageExtracts, setImageExtracts] = useState<any>({});
|
const [imageExtracts, setImageExtracts] = useState<any>({});
|
||||||
|
|
||||||
|
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({
|
const { getRootProps, getInputProps } = useDropzone({
|
||||||
accept: {
|
accept: {
|
||||||
"video/*": [],
|
"video/*": [],
|
||||||
@ -302,6 +311,8 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
code,
|
code,
|
||||||
videoType: file?.type || "video/mp4",
|
videoType: file?.type || "video/mp4",
|
||||||
filename: `${alphanumericString.trim()}.${fileExtension}`,
|
filename: `${alphanumericString.trim()}.${fileExtension}`,
|
||||||
|
fileSize: file?.size || 0,
|
||||||
|
duration: videoDurations.value[i],
|
||||||
};
|
};
|
||||||
|
|
||||||
const metadescription =
|
const metadescription =
|
||||||
@ -818,11 +829,14 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{files.map((file, index) => {
|
{files.map((file, index) => {
|
||||||
|
assembleVideoDurations();
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={index}>
|
<React.Fragment key={index}>
|
||||||
<FrameExtractor
|
<FrameExtractor
|
||||||
videoFile={file.file}
|
videoFile={file.file}
|
||||||
onFramesExtracted={imgs => onFramesExtracted(imgs, index)}
|
onFramesExtracted={imgs => onFramesExtracted(imgs, index)}
|
||||||
|
videoDurations={videoDurations}
|
||||||
|
index={index}
|
||||||
/>
|
/>
|
||||||
<Typography>{file?.file?.name}</Typography>
|
<Typography>{file?.file?.name}</Typography>
|
||||||
{!isCheckSameCoverImage && (
|
{!isCheckSameCoverImage && (
|
||||||
@ -1159,7 +1173,7 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
<TextEditor
|
<TextEditor
|
||||||
inlineContent={playlistDescription}
|
inlineContent={playlistDescription}
|
||||||
setInlineContent={value => {
|
setInlineContent={(value: string) => {
|
||||||
setPlaylistDescription(value);
|
setPlaylistDescription(value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -4,6 +4,12 @@ import { Grid } from "@mui/material";
|
|||||||
import { useFetchVideos } from "../hooks/useFetchVideos.tsx";
|
import { useFetchVideos } from "../hooks/useFetchVideos.tsx";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { RootState } from "../state/store.ts";
|
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 = () => {
|
export const StatsData = () => {
|
||||||
const StatsCol = styled(Grid)(({ theme }) => ({
|
const StatsCol = styled(Grid)(({ theme }) => ({
|
||||||
@ -14,44 +20,43 @@ export const StatsData = () => {
|
|||||||
backgroundColor: theme.palette.background.default,
|
backgroundColor: theme.palette.background.default,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const {
|
const { getVideosCount } = useFetchVideos();
|
||||||
getVideos,
|
|
||||||
getNewVideos,
|
|
||||||
checkNewVideos,
|
|
||||||
getVideosFiltered,
|
|
||||||
getVideosCount,
|
|
||||||
} = useFetchVideos();
|
|
||||||
|
|
||||||
const persistReducer = useSelector((state: RootState) => state.persist);
|
const showValueIfExists = (value: number) => {
|
||||||
const totalVideosPublished = useSelector(
|
return value > 0 ? "inline" : "none";
|
||||||
(state: RootState) => state.global.totalVideosPublished
|
};
|
||||||
);
|
|
||||||
const totalNamesPublished = useSelector(
|
|
||||||
(state: RootState) => state.global.totalNamesPublished
|
|
||||||
);
|
|
||||||
const videosPerNamePublished = useSelector(
|
|
||||||
(state: RootState) => state.global.videosPerNamePublished
|
|
||||||
);
|
|
||||||
|
|
||||||
|
const showStats = useSelector((state: RootState) => state.persist.showStats);
|
||||||
|
const showVideoCount = showValueIfExists(totalVideosPublished.value);
|
||||||
|
const showPublisherCount = showValueIfExists(totalNamesPublished.value);
|
||||||
|
const showAverage = showValueIfExists(videosPerNamePublished.value);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getVideosCount();
|
getVideosCount();
|
||||||
}, [getVideosCount]);
|
}, [getVideosCount]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StatsCol sx={{ display: persistReducer.showStats ? "block" : "none" }}>
|
<StatsCol sx={{ display: showStats ? "block" : "none" }}>
|
||||||
<div>
|
<div>
|
||||||
Videos:{" "}
|
Videos:{" "}
|
||||||
<span style={{ fontWeight: "bold" }}>{totalVideosPublished}</span>
|
<span style={{ fontWeight: "bold", display: showVideoCount }}>
|
||||||
|
{totalVideosPublished.value}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
Publishers:{" "}
|
Publishers:{" "}
|
||||||
<span style={{ fontWeight: "bold" }}>{totalNamesPublished}</span>
|
<span style={{ fontWeight: "bold", display: showPublisherCount }}>
|
||||||
|
{totalNamesPublished.value}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
Average:{" "}
|
Average:{" "}
|
||||||
<span style={{ fontWeight: "bold" }}>
|
<span
|
||||||
{videosPerNamePublished > 0 &&
|
style={{
|
||||||
Number(videosPerNamePublished).toFixed(0)}
|
fontWeight: "bold",
|
||||||
|
display: showAverage,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Number(videosPerNamePublished.value).toFixed(0)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</StatsCol>
|
</StatsCol>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Button, ButtonProps, Tooltip } from "@mui/material";
|
import { Button, ButtonProps, Tooltip } from "@mui/material";
|
||||||
import { MouseEvent, useEffect, useState } from "react";
|
import { MouseEvent, useEffect, useState } from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
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 { RootState } from "../../../state/store.ts";
|
||||||
import {
|
import {
|
||||||
subscribe,
|
subscribe,
|
||||||
|
@ -1,20 +1,38 @@
|
|||||||
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 }) => {
|
export interface FrameExtractorProps {
|
||||||
|
videoFile: File;
|
||||||
|
onFramesExtracted: (imgs, index?: number) => Promise<void>;
|
||||||
|
videoDurations?: Signal<number[]>;
|
||||||
|
index?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FrameExtractor = ({
|
||||||
|
videoFile,
|
||||||
|
onFramesExtracted,
|
||||||
|
videoDurations,
|
||||||
|
index,
|
||||||
|
}: FrameExtractorProps) => {
|
||||||
|
useSignals();
|
||||||
const videoRef = useRef(null);
|
const videoRef = useRef(null);
|
||||||
const [durations, setDurations] = useState([]);
|
const [durations, setDurations] = useState([]);
|
||||||
const canvasRef = useRef(null);
|
const canvasRef = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const video = videoRef.current;
|
const video = videoRef.current;
|
||||||
video.addEventListener('loadedmetadata', () => {
|
video.addEventListener("loadedmetadata", () => {
|
||||||
const duration = video.duration;
|
const duration = video.duration;
|
||||||
if (isFinite(duration)) {
|
if (isFinite(duration)) {
|
||||||
// Proceed with your logic
|
// Proceed with your logic
|
||||||
|
|
||||||
console.log('duration', duration)
|
const newVideoDurations = [...videoDurations.value];
|
||||||
|
newVideoDurations[index] = duration;
|
||||||
|
videoDurations.value = [...newVideoDurations];
|
||||||
|
|
||||||
const section = duration / 4;
|
const section = duration / 4;
|
||||||
let timestamps = [];
|
const timestamps = [];
|
||||||
|
|
||||||
for (let i = 0; i < 4; i++) {
|
for (let i = 0; i < 4; i++) {
|
||||||
const randomTime = Math.random() * section + i * section;
|
const randomTime = Math.random() * section + i * section;
|
||||||
@ -23,13 +41,11 @@ export const FrameExtractor = ({ videoFile, onFramesExtracted }) => {
|
|||||||
|
|
||||||
setDurations(timestamps);
|
setDurations(timestamps);
|
||||||
} else {
|
} else {
|
||||||
onFramesExtracted([])
|
onFramesExtracted([]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [videoFile]);
|
}, [videoFile]);
|
||||||
|
|
||||||
console.log({durations})
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (durations.length === 4) {
|
if (durations.length === 4) {
|
||||||
extractFrames();
|
extractFrames();
|
||||||
@ -45,33 +61,32 @@ export const FrameExtractor = ({ videoFile, onFramesExtracted }) => {
|
|||||||
const canvas = canvasRef.current;
|
const canvas = canvasRef.current;
|
||||||
canvas.width = video.videoWidth;
|
canvas.width = video.videoWidth;
|
||||||
canvas.height = video.videoHeight;
|
canvas.height = video.videoHeight;
|
||||||
const context = canvas.getContext('2d');
|
const context = canvas.getContext("2d");
|
||||||
|
|
||||||
let frameData = [];
|
const frameData = [];
|
||||||
|
|
||||||
for (const time of durations) {
|
for (const time of durations) {
|
||||||
await new Promise<void>((resolve) => {
|
await new Promise<void>(resolve => {
|
||||||
video.currentTime = time;
|
video.currentTime = time;
|
||||||
const onSeeked = () => {
|
const onSeeked = () => {
|
||||||
context.drawImage(video, 0, 0, canvas.width, canvas.height);
|
context.drawImage(video, 0, 0, canvas.width, canvas.height);
|
||||||
canvas.toBlob(blob => {
|
canvas.toBlob(blob => {
|
||||||
frameData.push(blob);
|
frameData.push(blob);
|
||||||
resolve();
|
resolve();
|
||||||
}, 'image/png');
|
}, "image/png");
|
||||||
video.removeEventListener('seeked', onSeeked);
|
video.removeEventListener("seeked", onSeeked);
|
||||||
};
|
};
|
||||||
video.addEventListener('seeked', onSeeked, { once: true });
|
video.addEventListener("seeked", onSeeked, { once: true });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onFramesExtracted(frameData);
|
onFramesExtracted(frameData);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<video ref={videoRef} style={{ display: 'none' }} src={fileUrl}></video>
|
<video ref={videoRef} style={{ display: "none" }} src={fileUrl}></video>
|
||||||
<canvas ref={canvasRef} style={{ display: 'none' }}></canvas>
|
<canvas ref={canvasRef} style={{ display: "none" }}></canvas>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -2,6 +2,7 @@ import { Box, CircularProgress, Typography } from "@mui/material";
|
|||||||
import { setVideoPlaying } from "../../../../state/features/globalSlice.ts";
|
import { setVideoPlaying } from "../../../../state/features/globalSlice.ts";
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from "react-redux";
|
||||||
import { PlayArrow } from "@mui/icons-material";
|
import { PlayArrow } from "@mui/icons-material";
|
||||||
|
import { formatTime } from "../../../../utils/numberFunctions.ts";
|
||||||
import { useVideoContext } from "./VideoContext.ts";
|
import { useVideoContext } from "./VideoContext.ts";
|
||||||
|
|
||||||
export const LoadingVideo = () => {
|
export const LoadingVideo = () => {
|
||||||
@ -13,6 +14,7 @@ export const LoadingVideo = () => {
|
|||||||
canPlay,
|
canPlay,
|
||||||
from,
|
from,
|
||||||
togglePlay,
|
togglePlay,
|
||||||
|
duration,
|
||||||
} = useVideoContext();
|
} = useVideoContext();
|
||||||
|
|
||||||
const getDownloadProgress = (current: number, total: number) => {
|
const getDownloadProgress = (current: number, total: number) => {
|
||||||
@ -83,7 +85,20 @@ export const LoadingVideo = () => {
|
|||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{((!src && !isLoading.value) || (!startPlay.value && !canPlay.value)) && (
|
{((!src && !isLoading.value) || (!startPlay.value && !canPlay.value)) && (
|
||||||
|
<>
|
||||||
|
{duration && (
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
right={0}
|
||||||
|
bottom={0}
|
||||||
|
bgcolor="#202020"
|
||||||
|
zIndex={999}
|
||||||
|
>
|
||||||
|
<Typography color="white">{formatTime(duration)}</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
<Box
|
<Box
|
||||||
position="absolute"
|
position="absolute"
|
||||||
top={0}
|
top={0}
|
||||||
@ -113,6 +128,7 @@ export const LoadingVideo = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useSignal } from "@preact/signals-react";
|
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 { useEffect } from "react";
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
@ -35,6 +35,7 @@ export const useVideoControlsState = (
|
|||||||
progress,
|
progress,
|
||||||
videoObjectFit,
|
videoObjectFit,
|
||||||
canPlay,
|
canPlay,
|
||||||
|
isMobileView,
|
||||||
} = videoPlayerState;
|
} = videoPlayerState;
|
||||||
const { identifier, autoPlay } = props;
|
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 () => {
|
const reloadVideo = async () => {
|
||||||
if (!videoRef.current || !src) return;
|
if (!videoRef.current || !src) return;
|
||||||
const currentTime = videoRef.current.currentTime;
|
const currentTime = videoRef.current.currentTime;
|
||||||
@ -154,6 +130,7 @@ export const useVideoControlsState = (
|
|||||||
if (firstPlay.value) setPlaying(true); // makes the video play when fully loaded
|
if (firstPlay.value) setPlaying(true); // makes the video play when fully loaded
|
||||||
firstPlay.value = false;
|
firstPlay.value = false;
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
|
updatePlaybackRate(persistSelector.playbackRate);
|
||||||
};
|
};
|
||||||
|
|
||||||
const setPlaying = async (setPlay: boolean) => {
|
const setPlaying = async (setPlay: boolean) => {
|
||||||
@ -380,6 +357,14 @@ export const useVideoControlsState = (
|
|||||||
}
|
}
|
||||||
}, [videoPlaying, identifier, src]);
|
}, [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 {
|
return {
|
||||||
reloadVideo,
|
reloadVideo,
|
||||||
togglePlay,
|
togglePlay,
|
||||||
@ -387,7 +372,6 @@ export const useVideoControlsState = (
|
|||||||
increaseSpeed,
|
increaseSpeed,
|
||||||
togglePictureInPicture,
|
togglePictureInPicture,
|
||||||
toggleFullscreen,
|
toggleFullscreen,
|
||||||
formatTime,
|
|
||||||
keyboardShortcutsUp,
|
keyboardShortcutsUp,
|
||||||
keyboardShortcutsDown,
|
keyboardShortcutsDown,
|
||||||
handleCanPlay,
|
handleCanPlay,
|
||||||
|
@ -8,15 +8,13 @@ import {
|
|||||||
VolumeUp,
|
VolumeUp,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
import { IconButton, Slider, Typography } from "@mui/material";
|
import { IconButton, Slider, Typography } from "@mui/material";
|
||||||
import { useSignalEffect, useSignals } from "@preact/signals-react/runtime";
|
import { formatTime } from "../../../../utils/numberFunctions.ts";
|
||||||
import { useEffect } from "react";
|
|
||||||
|
|
||||||
import { ControlsContainer } from "../VideoPlayer-styles.ts";
|
import { ControlsContainer } from "../VideoPlayer-styles.ts";
|
||||||
import { MobileControls } from "./MobileControls.tsx";
|
import { MobileControls } from "./MobileControls.tsx";
|
||||||
import { useVideoContext } from "./VideoContext.ts";
|
import { useVideoContext } from "./VideoContext.ts";
|
||||||
|
|
||||||
export const VideoControls = () => {
|
export const VideoControls = () => {
|
||||||
useSignals();
|
|
||||||
const {
|
const {
|
||||||
reloadVideo,
|
reloadVideo,
|
||||||
togglePlay,
|
togglePlay,
|
||||||
@ -24,7 +22,6 @@ export const VideoControls = () => {
|
|||||||
increaseSpeed,
|
increaseSpeed,
|
||||||
togglePictureInPicture,
|
togglePictureInPicture,
|
||||||
toggleFullscreen,
|
toggleFullscreen,
|
||||||
formatTime,
|
|
||||||
toggleMute,
|
toggleMute,
|
||||||
onProgressChange,
|
onProgressChange,
|
||||||
toggleRef,
|
toggleRef,
|
||||||
@ -40,14 +37,6 @@ export const VideoControls = () => {
|
|||||||
showControlsFullScreen,
|
showControlsFullScreen,
|
||||||
} = useVideoContext();
|
} = 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 =
|
const showMobileControls =
|
||||||
isMobileView.value && canPlay.value && showControlsFullScreen.value;
|
isMobileView.value && canPlay.value && showControlsFullScreen.value;
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { useSignals } from "@preact/signals-react/runtime";
|
|
||||||
import CSS from "csstype";
|
import CSS from "csstype";
|
||||||
import { forwardRef } from "react";
|
import { forwardRef } from "react";
|
||||||
import { LoadingVideo } from "./Components/LoadingVideo.tsx";
|
import { LoadingVideo } from "./Components/LoadingVideo.tsx";
|
||||||
@ -26,6 +25,7 @@ export interface VideoPlayerProps {
|
|||||||
onEnd?: () => void;
|
onEnd?: () => void;
|
||||||
autoPlay?: boolean;
|
autoPlay?: boolean;
|
||||||
style?: CSS.Properties;
|
style?: CSS.Properties;
|
||||||
|
duration?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type videoRefType = {
|
export type videoRefType = {
|
||||||
@ -34,7 +34,6 @@ export type videoRefType = {
|
|||||||
};
|
};
|
||||||
export const VideoPlayer = forwardRef<videoRefType, VideoPlayerProps>(
|
export const VideoPlayer = forwardRef<videoRefType, VideoPlayerProps>(
|
||||||
(props: VideoPlayerProps, ref) => {
|
(props: VideoPlayerProps, ref) => {
|
||||||
useSignals();
|
|
||||||
const contextData = useContextData(props, ref);
|
const contextData = useContextData(props, ref);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -56,6 +55,7 @@ export const VideoPlayer = forwardRef<videoRefType, VideoPlayerProps>(
|
|||||||
startPlay,
|
startPlay,
|
||||||
videoObjectFit,
|
videoObjectFit,
|
||||||
showControlsFullScreen,
|
showControlsFullScreen,
|
||||||
|
duration,
|
||||||
} = contextData;
|
} = contextData;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
VolumeOff,
|
VolumeOff,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
import { styled } from "@mui/system";
|
import { styled } from "@mui/system";
|
||||||
|
import { formatTime } from "../../../utils/numberFunctions.ts";
|
||||||
import { MyContext } from "../../../wrappers/DownloadWrapper.tsx";
|
import { MyContext } from "../../../wrappers/DownloadWrapper.tsx";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { RootState } from "../../../state/store.ts";
|
import { RootState } from "../../../state/store.ts";
|
||||||
@ -250,31 +251,6 @@ export const VideoPlayerGlobal: React.FC<VideoPlayerProps> = ({
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
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 () => {
|
const reloadVideo = async () => {
|
||||||
if (!videoRef.current) return;
|
if (!videoRef.current) return;
|
||||||
const src = videoRef.current.src;
|
const src = videoRef.current.src;
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
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 {
|
import {
|
||||||
addVideos,
|
addVideos,
|
||||||
addToHashMap,
|
addToHashMap,
|
||||||
@ -11,13 +16,7 @@ import {
|
|||||||
upsertFilteredVideos,
|
upsertFilteredVideos,
|
||||||
removeFromHashMap,
|
removeFromHashMap,
|
||||||
} from "../state/features/videoSlice";
|
} from "../state/features/videoSlice";
|
||||||
import {
|
import { setIsLoadingGlobal } from "../state/features/globalSlice";
|
||||||
setIsLoadingGlobal,
|
|
||||||
setUserAvatarHash,
|
|
||||||
setTotalVideosPublished,
|
|
||||||
setTotalNamesPublished,
|
|
||||||
setVideosPerNamePublished,
|
|
||||||
} from "../state/features/globalSlice";
|
|
||||||
import { RootState } from "../state/store";
|
import { RootState } from "../state/store";
|
||||||
import { fetchAndEvaluateVideos } from "../utils/fetchVideos";
|
import { fetchAndEvaluateVideos } from "../utils/fetchVideos";
|
||||||
import { RequestQueue } from "../utils/queue";
|
import { RequestQueue } from "../utils/queue";
|
||||||
@ -390,14 +389,11 @@ export const useFetchVideos = () => {
|
|||||||
});
|
});
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
|
|
||||||
const totalVideosPublished = responseData.length;
|
totalVideosPublished.value = responseData.length;
|
||||||
const uniqueNames = new Set(responseData.map(video => video.name));
|
const uniqueNames = new Set(responseData.map(video => video.name));
|
||||||
const totalNamesPublished = uniqueNames.size;
|
totalNamesPublished.value = uniqueNames.size;
|
||||||
const videosPerNamePublished = totalVideosPublished / totalNamesPublished;
|
videosPerNamePublished.value =
|
||||||
|
totalVideosPublished.value / totalNamesPublished.value;
|
||||||
dispatch(setTotalVideosPublished(totalVideosPublished));
|
|
||||||
dispatch(setTotalNamesPublished(totalNamesPublished));
|
|
||||||
dispatch(setVideosPerNamePublished(videosPerNamePublished));
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log({ error });
|
console.log({ error });
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { styled } from "@mui/system";
|
import { styled } from "@mui/system";
|
||||||
import { Box, Grid, Typography, Checkbox } from "@mui/material";
|
import { Box, Grid, Typography, Checkbox } from "@mui/material";
|
||||||
|
import { fontSizeMedium } from "../../../constants/Misc.ts";
|
||||||
|
|
||||||
export const VideoContentContainer = styled(Box)(({ theme }) => ({
|
export const VideoContentContainer = styled(Box)(({ theme }) => ({
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@ -14,7 +15,7 @@ export const VideoPlayerContainer = styled(Box)(({ theme }) => ({
|
|||||||
|
|
||||||
export const VideoTitle = styled(Typography)(({ theme }) => ({
|
export const VideoTitle = styled(Typography)(({ theme }) => ({
|
||||||
fontFamily: "Raleway",
|
fontFamily: "Raleway",
|
||||||
fontSize: "20px",
|
fontSize: fontSizeMedium,
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
wordBreak: "break-word",
|
wordBreak: "break-word",
|
||||||
}));
|
}));
|
||||||
|
@ -14,6 +14,7 @@ import {
|
|||||||
SUPER_LIKE_BASE,
|
SUPER_LIKE_BASE,
|
||||||
} from "../../../constants/Identifiers.ts";
|
} from "../../../constants/Identifiers.ts";
|
||||||
import {
|
import {
|
||||||
|
fontSizeMedium,
|
||||||
minPriceSuperlike,
|
minPriceSuperlike,
|
||||||
titleFormatterOnSave,
|
titleFormatterOnSave,
|
||||||
} from "../../../constants/Misc.ts";
|
} from "../../../constants/Misc.ts";
|
||||||
@ -21,6 +22,7 @@ import { useFetchSuperLikes } from "../../../hooks/useFetchSuperLikes.tsx";
|
|||||||
import { setIsLoadingGlobal } from "../../../state/features/globalSlice.ts";
|
import { setIsLoadingGlobal } from "../../../state/features/globalSlice.ts";
|
||||||
import { addToHashMap } from "../../../state/features/videoSlice.ts";
|
import { addToHashMap } from "../../../state/features/videoSlice.ts";
|
||||||
import { RootState } from "../../../state/store.ts";
|
import { RootState } from "../../../state/store.ts";
|
||||||
|
import { formatBytes } from "../../../utils/numberFunctions.ts";
|
||||||
import { formatDate } from "../../../utils/time.ts";
|
import { formatDate } from "../../../utils/time.ts";
|
||||||
import { VideoActionsBar } from "./VideoActionsBar.tsx";
|
import { VideoActionsBar } from "./VideoActionsBar.tsx";
|
||||||
import {
|
import {
|
||||||
@ -62,7 +64,6 @@ export const VideoContent = () => {
|
|||||||
superLikeList,
|
superLikeList,
|
||||||
setSuperLikeList,
|
setSuperLikeList,
|
||||||
} = useVideoContentState();
|
} = useVideoContentState();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box
|
<Box
|
||||||
@ -87,6 +88,7 @@ export const VideoContent = () => {
|
|||||||
videoContainer: { aspectRatio: "16 / 9" },
|
videoContainer: { aspectRatio: "16 / 9" },
|
||||||
video: { aspectRatio: "16 / 9" },
|
video: { aspectRatio: "16 / 9" },
|
||||||
}}
|
}}
|
||||||
|
duration={videoData?.duration}
|
||||||
/>
|
/>
|
||||||
</VideoPlayerContainer>
|
</VideoPlayerContainer>
|
||||||
) : isVideoLoaded ? (
|
) : isVideoLoaded ? (
|
||||||
@ -128,7 +130,17 @@ export const VideoContent = () => {
|
|||||||
{videoData?.title}
|
{videoData?.title}
|
||||||
</VideoTitle>
|
</VideoTitle>
|
||||||
</Box>
|
</Box>
|
||||||
|
{videoData?.fileSize && (
|
||||||
|
<Typography
|
||||||
|
variant="h1"
|
||||||
|
sx={{
|
||||||
|
fontSize: "90%",
|
||||||
|
}}
|
||||||
|
color={"green"}
|
||||||
|
>
|
||||||
|
{formatBytes(videoData.fileSize, 2, "Decimal")}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
{videoData?.created && (
|
{videoData?.created && (
|
||||||
<Typography
|
<Typography
|
||||||
variant="h6"
|
variant="h6"
|
||||||
@ -140,7 +152,6 @@ export const VideoContent = () => {
|
|||||||
{formatDate(videoData.created)}
|
{formatDate(videoData.created)}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Spacer height="30px" />
|
<Spacer height="30px" />
|
||||||
{videoData?.fullDescription && (
|
{videoData?.fullDescription && (
|
||||||
<Box
|
<Box
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import BlockIcon from "@mui/icons-material/Block";
|
import BlockIcon from "@mui/icons-material/Block";
|
||||||
import EditIcon from "@mui/icons-material/Edit";
|
import EditIcon from "@mui/icons-material/Edit";
|
||||||
import { Avatar, Box, Tooltip, useTheme } from "@mui/material";
|
import { Avatar, Box, Tooltip, Typography, useTheme } from "@mui/material";
|
||||||
import React, { useMemo, useState } from "react";
|
import React, { useMemo, useState } from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
@ -14,6 +14,7 @@ import {
|
|||||||
Video,
|
Video,
|
||||||
} from "../../../state/features/videoSlice.ts";
|
} from "../../../state/features/videoSlice.ts";
|
||||||
import { RootState } from "../../../state/store.ts";
|
import { RootState } from "../../../state/store.ts";
|
||||||
|
import { formatTime } from "../../../utils/numberFunctions.ts";
|
||||||
import { formatDate } from "../../../utils/time.ts";
|
import { formatDate } from "../../../utils/time.ts";
|
||||||
import { VideoCardImageContainer } from "./VideoCardImageContainer.tsx";
|
import { VideoCardImageContainer } from "./VideoCardImageContainer.tsx";
|
||||||
import {
|
import {
|
||||||
@ -74,7 +75,6 @@ export const VideoList = ({ videos }: VideoListProps) => {
|
|||||||
videoObj = existingVideo;
|
videoObj = existingVideo;
|
||||||
hasHash = true;
|
hasHash = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// nb. this prevents showing metadata for a video which
|
// nb. this prevents showing metadata for a video which
|
||||||
// belongs to a different user
|
// belongs to a different user
|
||||||
if (
|
if (
|
||||||
@ -147,7 +147,6 @@ export const VideoList = ({ videos }: VideoListProps) => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<VideoCardTitle>{videoObj?.title}</VideoCardTitle>
|
<VideoCardTitle>{videoObj?.title}</VideoCardTitle>
|
||||||
|
|
||||||
<BottomParent>
|
<BottomParent>
|
||||||
<NameContainer
|
<NameContainer
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
@ -237,6 +236,19 @@ export const VideoList = ({ videos }: VideoListProps) => {
|
|||||||
navigate(`/video/${videoObj?.user}/${videoObj?.id}`);
|
navigate(`/video/${videoObj?.user}/${videoObj?.id}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{videoObj?.duration && (
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
right={0}
|
||||||
|
bottom={0}
|
||||||
|
bgcolor="#202020"
|
||||||
|
zIndex={999}
|
||||||
|
>
|
||||||
|
<Typography color="white">
|
||||||
|
{formatTime(videoObj.duration)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
<VideoCardImageContainer
|
<VideoCardImageContainer
|
||||||
width={266}
|
width={266}
|
||||||
height={150}
|
height={150}
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
import { createSlice } from '@reduxjs/toolkit'
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
|
|
||||||
interface GlobalState {
|
interface GlobalState {
|
||||||
isLoadingGlobal: boolean
|
isLoadingGlobal: boolean;
|
||||||
downloads: any
|
downloads: any;
|
||||||
userAvatarHash: Record<string, string>
|
userAvatarHash: Record<string, string>;
|
||||||
publishNames: string[] | null
|
publishNames: string[] | null;
|
||||||
videoPlaying: any | null
|
videoPlaying: any | null;
|
||||||
superlikelistAll: any[]
|
superlikelistAll: any[];
|
||||||
totalVideosPublished: number
|
|
||||||
totalNamesPublished: number
|
|
||||||
videosPerNamePublished: number
|
|
||||||
}
|
}
|
||||||
const initialState: GlobalState = {
|
const initialState: GlobalState = {
|
||||||
isLoadingGlobal: false,
|
isLoadingGlobal: false,
|
||||||
@ -19,56 +15,44 @@ const initialState: GlobalState = {
|
|||||||
publishNames: null,
|
publishNames: null,
|
||||||
videoPlaying: null,
|
videoPlaying: null,
|
||||||
superlikelistAll: [],
|
superlikelistAll: [],
|
||||||
totalVideosPublished: null,
|
};
|
||||||
totalNamesPublished: null,
|
|
||||||
videosPerNamePublished: null
|
|
||||||
}
|
|
||||||
|
|
||||||
export const globalSlice = createSlice({
|
export const globalSlice = createSlice({
|
||||||
name: 'global',
|
name: "global",
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
setIsLoadingGlobal: (state, action) => {
|
setIsLoadingGlobal: (state, action) => {
|
||||||
state.isLoadingGlobal = action.payload
|
state.isLoadingGlobal = action.payload;
|
||||||
},
|
},
|
||||||
setAddToDownloads: (state, action) => {
|
setAddToDownloads: (state, action) => {
|
||||||
const download = action.payload
|
const download = action.payload;
|
||||||
state.downloads[download.identifier] = download
|
state.downloads[download.identifier] = download;
|
||||||
},
|
},
|
||||||
updateDownloads: (state, action) => {
|
updateDownloads: (state, action) => {
|
||||||
const { identifier } = action.payload
|
const { identifier } = action.payload;
|
||||||
const download = action.payload
|
const download = action.payload;
|
||||||
state.downloads[identifier] = {
|
state.downloads[identifier] = {
|
||||||
...state.downloads[identifier],
|
...state.downloads[identifier],
|
||||||
...download
|
...download,
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
setUserAvatarHash: (state, action) => {
|
setUserAvatarHash: (state, action) => {
|
||||||
const avatar = action.payload
|
const avatar = action.payload;
|
||||||
if (avatar?.name && avatar?.url) {
|
if (avatar?.name && avatar?.url) {
|
||||||
state.userAvatarHash[avatar?.name] = avatar?.url
|
state.userAvatarHash[avatar?.name] = avatar?.url;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addPublishNames: (state, action) => {
|
addPublishNames: (state, action) => {
|
||||||
state.publishNames = action.payload
|
state.publishNames = action.payload;
|
||||||
},
|
},
|
||||||
setVideoPlaying: (state, action) => {
|
setVideoPlaying: (state, action) => {
|
||||||
state.videoPlaying = action.payload
|
state.videoPlaying = action.payload;
|
||||||
},
|
},
|
||||||
setSuperlikesAll: (state, action) => {
|
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 {
|
export const {
|
||||||
setIsLoadingGlobal,
|
setIsLoadingGlobal,
|
||||||
@ -78,9 +62,6 @@ export const {
|
|||||||
addPublishNames,
|
addPublishNames,
|
||||||
setVideoPlaying,
|
setVideoPlaying,
|
||||||
setSuperlikesAll,
|
setSuperlikesAll,
|
||||||
setTotalVideosPublished,
|
} = globalSlice.actions;
|
||||||
setTotalNamesPublished,
|
|
||||||
setVideosPerNamePublished
|
|
||||||
} = globalSlice.actions
|
|
||||||
|
|
||||||
export default globalSlice.reducer
|
export default globalSlice.reducer;
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
export const truncateNumber = (value: string | number, sigDigits: number) => {
|
export const truncateNumber = (value: string | number, sigDigits: number) => {
|
||||||
return Number(value).toFixed(sigDigits);
|
return Number(value).toFixed(sigDigits);
|
||||||
};
|
};
|
||||||
@ -21,3 +19,45 @@ export const setNumberWithinBounds = (
|
|||||||
export const numberToInt = (num: number) => {
|
export const numberToInt = (num: number) => {
|
||||||
return Math.floor(num);
|
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;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user