diff --git a/package.json b/package.json
index 92df552..e168cc3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "qapp-core",
- "version": "1.0.34",
+ "version": "1.0.35",
"description": "Qortal's core React library with global state, UI components, and utilities",
"main": "dist/index.js",
"module": "dist/index.mjs",
diff --git a/src/components/VideoPlayer/LoadingVideo.tsx b/src/components/VideoPlayer/LoadingVideo.tsx
index 78d77bd..cce83f7 100644
--- a/src/components/VideoPlayer/LoadingVideo.tsx
+++ b/src/components/VideoPlayer/LoadingVideo.tsx
@@ -1,31 +1,42 @@
-import { alpha, Box, Button, CircularProgress, IconButton, Typography } from "@mui/material";
+import {
+ alpha,
+ Box,
+ Button,
+ CircularProgress,
+ IconButton,
+ Typography,
+} from "@mui/material";
import { PlayArrow } from "@mui/icons-material";
import { Status } from "../../state/publishes";
-
interface LoadingVideoProps {
- status: Status | null
- percentLoaded: number
- isReady: boolean
- isLoading: boolean
- togglePlay: ()=> void
- startPlay: boolean,
- downloadResource: ()=> void
+ status: Status | null;
+ percentLoaded: number;
+ isReady: boolean;
+ isLoading: boolean;
+ togglePlay: () => void;
+ startPlay: boolean;
+ downloadResource: () => void;
}
export const LoadingVideo = ({
- status, percentLoaded, isReady, isLoading, togglePlay, startPlay, downloadResource
+ status,
+ percentLoaded,
+ isReady,
+ isLoading,
+ togglePlay,
+ startPlay,
+ downloadResource,
}: LoadingVideoProps) => {
-
const getDownloadProgress = (percentLoaded: number) => {
const progress = percentLoaded;
return Number.isNaN(progress) ? "" : progress.toFixed(0) + "%";
};
- if(status === 'READY') return null
+ if (status === "READY") return null;
return (
<>
- {isLoading && status !== 'INITIAL' && (
+ {isLoading && status !== "INITIAL" && (
{status !== "NOT_PUBLISHED" && status !== "FAILED_TO_DOWNLOAD" && (
-
-
+
)}
{status && (
-
{status === "NOT_PUBLISHED" ? (
<>Video file was not published. Please inform the publisher!>
) : status === "REFETCHING" ? (
<>
- <>
- {getDownloadProgress(
- percentLoaded
- )}
- >
+ <>{getDownloadProgress(percentLoaded)}>
<> Refetching in 25 seconds>
>
) : status === "DOWNLOADED" ? (
<>Download Completed: building video...>
- ) : status === "FAILED_TO_DOWNLOAD" ? (
+ ) : status === "FAILED_TO_DOWNLOAD" ? (
<>Unable to fetch video chunks from peers>
) : (
- <>
- {getDownloadProgress(
- percentLoaded
- )}
- >
+ <>{getDownloadProgress(percentLoaded)}>
)}
)}
- {status === 'FAILED_TO_DOWNLOAD' && (
-
+ {status === "FAILED_TO_DOWNLOAD" && (
+
)}
)}
- {(status === 'INITIAL') && (
+ {status === "INITIAL" && (
<>
{
togglePlay();
}}
-
sx={{
cursor: "pointer",
- position:"absolute",
- top:0,
- left:0,
- right:0,
- bottom:0,
- zIndex: 501,
- background: 'rgba(0,0,0,0.3)',
- padding: '0px',
- borderRadius: "0px",
-
+ position: "absolute",
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0,
+ zIndex: 501,
+ background: "rgba(0,0,0,0.3)",
+ padding: "0px",
+ borderRadius: "0px",
}}
>
void;
toggleFullscreen: () => void;
setProgressRelative: (val: number) => void;
- setLocalProgress: (val: number)=> void;
- resetHideTimeout: ()=> void
+ setLocalProgress: (val: number) => void;
+ resetHideTimeout: () => void;
}
export const MobileControls = ({
showControlsMobile,
@@ -37,7 +37,7 @@ export const MobileControls = ({
toggleFullscreen,
setProgressRelative,
setLocalProgress,
- resetHideTimeout
+ resetHideTimeout,
}: MobileControlsProps) => {
return (
{
e.stopPropagation();
openPlaybackMenu();
}}
>
-
+ color: "white",
+ }}
+ />
{
e.stopPropagation();
@@ -126,7 +128,7 @@ export const MobileControls = ({
@@ -135,9 +137,9 @@ export const MobileControls = ({
sx={{
opacity: 1,
zIndex: 2,
- background: 'rgba(0,0,0,0.3)',
- borderRadius: '50%',
- padding: '10px'
+ background: "rgba(0,0,0,0.3)",
+ borderRadius: "50%",
+ padding: "10px",
}}
onClick={(e) => {
e.stopPropagation();
@@ -147,7 +149,7 @@ export const MobileControls = ({
@@ -157,9 +159,9 @@ export const MobileControls = ({
sx={{
opacity: 1,
zIndex: 2,
- background: 'rgba(0,0,0,0.3)',
- borderRadius: '50%',
- padding: '10px'
+ background: "rgba(0,0,0,0.3)",
+ borderRadius: "50%",
+ padding: "10px",
}}
onClick={(e) => {
e.stopPropagation();
@@ -169,7 +171,7 @@ export const MobileControls = ({
@@ -178,9 +180,9 @@ export const MobileControls = ({
sx={{
opacity: 1,
zIndex: 2,
- background: 'rgba(0,0,0,0.3)',
- borderRadius: '50%',
- padding: '10px'
+ background: "rgba(0,0,0,0.3)",
+ borderRadius: "50%",
+ padding: "10px",
}}
onClick={(e) => {
e.stopPropagation();
@@ -190,7 +192,7 @@ export const MobileControls = ({
@@ -206,19 +208,21 @@ export const MobileControls = ({
{
e.stopPropagation();
toggleFullscreen();
}}
>
-
+
-
-
+
+
void;
open: boolean;
onSelect: (subtitle: SubtitlePublishedData) => void;
subtitleBtnRef: any;
- currentSubTrack: null | string
- setDrawerOpenSubtitles: (val: boolean)=> void
- isFromDrawer: boolean
- exitFullscreen: ()=> void
+ currentSubTrack: null | string;
+ setDrawerOpenSubtitles: (val: boolean) => void;
+ isFromDrawer: boolean;
+ exitFullscreen: () => void;
}
export interface Subtitle {
language: string | null;
@@ -113,7 +112,7 @@ const SubtitleManagerComponent = ({
currentSubTrack,
setDrawerOpenSubtitles,
isFromDrawer = false,
- exitFullscreen
+ exitFullscreen,
}: SubtitleManagerProps) => {
const [mode, setMode] = useState(1);
const [isOpenPublish, setIsOpenPublish] = useState(false);
@@ -146,12 +145,13 @@ const SubtitleManagerComponent = ({
identifier: postIdSearch,
name,
limit: 0,
- includeMetadata: true
+ includeMetadata: true,
};
- const res = await lists.fetchResourcesResultsOnly(
- searchParams
+ const res = await lists.fetchResourcesResultsOnly(searchParams);
+ lists.addList(
+ `subs-${videoId}`,
+ res?.filter((item) => !!item?.metadata?.title) || []
);
- lists.addList(`subs-${videoId}`, res?.filter((item)=> !!item?.metadata?.title) || []);
} catch (error) {
console.error(error);
} finally {
@@ -175,38 +175,42 @@ const SubtitleManagerComponent = ({
getPublishedSubtitles,
]);
- const ref = useRef(null)
+ const ref = useRef(null);
- useEffect(()=> {
- if(open){
- ref?.current?.focus()
+ useEffect(() => {
+ if (open) {
+ ref?.current?.focus();
}
- }, [open])
+ }, [open]);
- console.log('isFromDrawer', )
+ console.log("isFromDrawer");
const handleBlur = (e: React.FocusEvent) => {
- if (!e.currentTarget.contains(e.relatedTarget) && !isOpenPublish && !isFromDrawer && open) {
- console.log('hello close')
- close();
- setIsOpenPublish(false)
- }
-};
+ if (
+ !e.currentTarget.contains(e.relatedTarget) &&
+ !isOpenPublish &&
+ !isFromDrawer &&
+ open
+ ) {
+ console.log("hello close");
+ close();
+ setIsOpenPublish(false);
+ }
+ };
const publishHandler = async (subtitles: Subtitle[]) => {
try {
const videoId = `${qortalMetadata?.service}-${qortalMetadata?.name}-${qortalMetadata?.identifier}`;
-
const name = auth?.name;
if (!name) return;
const resources: ResourceToPublish[] = [];
const tempResources: { qortalMetadata: QortalMetadata; data: any }[] = [];
for (const sub of subtitles) {
- const identifier = await identifierOperations.buildLooseIdentifier(
- ENTITY_SUBTITLE,
- videoId
- );
+ const identifier = await identifierOperations.buildLooseIdentifier(
+ ENTITY_SUBTITLE,
+ videoId
+ );
const data = {
subtitleData: sub.base64,
language: sub.language,
@@ -233,7 +237,7 @@ const SubtitleManagerComponent = ({
created: Date.now(),
metadata: {
title: sub.language || undefined,
- }
+ },
},
data: data,
});
@@ -260,33 +264,30 @@ const SubtitleManagerComponent = ({
};
const theme = useTheme();
- if(!open) return null
+ if (!open) return null;
return (
<>
{
- setIsOpenPublish(true)
-
+ setIsOpenPublish(true);
}}
>
-
-
-
>
);
};
-
interface PublisherSubtitlesProps {
publisherName: string;
subtitles: any[];
@@ -450,13 +446,13 @@ const PublisherSubtitles = ({
{!currentSubTrack ? : }
- {subtitles?.map((sub) => {
+ {subtitles?.map((sub, i) => {
return (
);
})}
@@ -725,6 +721,7 @@ const PublishSubtitles = ({
{mySubtitles?.map((sub, i) => {
return (
void;
currentSubtrack: null | string;
}
- const subtitlesStatus: Record = {}
+const subtitlesStatus: Record = {};
const Subtitle = ({ sub, onSelect, currentSubtrack }: SubProps) => {
- const [selectedToDownload, setSelectedToDownload] = useState(null)
- const [isReady, setIsReady] = useState(false)
- const { resource, isLoading, error, refetch } = usePublish(2, "JSON", sub, true);
+ const [isReady, setIsReady] = useState(false);
+ const { resource, isLoading, error, refetch } = usePublish(
+ 2,
+ "JSON",
+ sub,
+ true
+ );
const isSelected = currentSubtrack === resource?.data?.language;
- const [isGettingStatus, setIsGettingStatus] = useState(true)
- // useEffect(()=> {
- // if(resource?.data){
- // console.log('onselectdone')
- // onSelect(resource?.data)
- // }
- // }, [isSelected, resource?.data])
- const getStatus = useCallback(async (service: Service, name: string, identifier: string)=> {
- try {
- if(subtitlesStatus[`${service}-${name}-${identifier}`]){
- setIsReady(true)
- refetch()
- return
- }
-
- const response = await requestQueueGetStatus.enqueue(
- (): Promise => {
- return qortalRequest({
- action: 'GET_QDN_RESOURCE_STATUS',
- identifier,
- service,
- name,
- build: false
- })
- }
- );
- if(response?.status === 'READY'){
- setIsReady(true)
- subtitlesStatus[`${service}-${name}-${identifier}`] = true
- refetch()
+ const [isGettingStatus, setIsGettingStatus] = useState(true);
- }
- } catch (error) {
-
- } finally {
- setIsGettingStatus(false)
- }
- }, [])
+ const getStatus = useCallback(
+ async (service: Service, name: string, identifier: string) => {
+ try {
+ if (subtitlesStatus[`${service}-${name}-${identifier}`]) {
+ setIsReady(true);
+ refetch();
+ return;
+ }
- useEffect(()=> {
- if(sub?.service && sub?.name && sub?.identifier){
- getStatus(sub?.service, sub?.name, sub?.identifier)
+ const response = await requestQueueGetStatus.enqueue(
+ (): Promise => {
+ return qortalRequest({
+ action: "GET_QDN_RESOURCE_STATUS",
+ identifier,
+ service,
+ name,
+ build: false,
+ });
+ }
+ );
+ if (response?.status === "READY") {
+ setIsReady(true);
+ subtitlesStatus[`${service}-${name}-${identifier}`] = true;
+ refetch();
+ }
+ } catch (error) {
+ } finally {
+ setIsGettingStatus(false);
+ }
+ },
+ []
+ );
+
+ useEffect(() => {
+ if (sub?.service && sub?.name && sub?.identifier) {
+ getStatus(sub?.service, sub?.name, sub?.identifier);
}
- }, [sub?.identifier, sub?.name, sub?.service])
+ }, [sub?.identifier, sub?.name, sub?.service]);
return (
{
- if(resource?.data){
- onSelect(isSelected ? null : resource?.data)
-
+ if (resource?.data) {
+ onSelect(isSelected ? null : resource?.data);
} else {
- refetch()
+ refetch();
}
}}
sx={{
@@ -830,14 +826,23 @@ const Subtitle = ({ sub, onSelect, currentSubtrack }: SubProps) => {
justifyContent: "space-between",
}}
>
- {isGettingStatus && }
+ {isGettingStatus && (
+
+ )}
{!isGettingStatus && (
<>
{sub?.metadata?.title}
- {(!isLoading && !error && !resource?.data) ? : isLoading ? : isSelected ? : }
+ {!isLoading && !error && !resource?.data ? (
+
+ ) : isLoading ? (
+
+ ) : isSelected ? (
+
+ ) : (
+
+ )}
>
)}
-
);
};
diff --git a/src/components/VideoPlayer/TimelineActionsComponent.tsx b/src/components/VideoPlayer/TimelineActionsComponent.tsx
index 9de8002..7cc4941 100644
--- a/src/components/VideoPlayer/TimelineActionsComponent.tsx
+++ b/src/components/VideoPlayer/TimelineActionsComponent.tsx
@@ -1,41 +1,51 @@
-import React, { useCallback, useMemo, useState } from 'react'
-import { TimelineAction } from './VideoPlayer'
-import { alpha, Box, ButtonBase, Popover, Typography } from '@mui/material'
+import React, { useCallback, useMemo, useState } from "react";
+import { TimelineAction } from "./VideoPlayer";
+import { alpha, ButtonBase, Typography } from "@mui/material";
interface TimelineActionsComponentProps {
-timelineActions: TimelineAction[]
-progress: number
-containerRef: any
-seekTo: (time: number)=> void
-isVideoPlayerSmall: boolean
-
+ timelineActions: TimelineAction[];
+ progress: number;
+ containerRef: any;
+ seekTo: (time: number) => void;
+ isVideoPlayerSmall: boolean;
}
-const placementStyles: Record, React.CSSProperties> = {
- 'TOP-RIGHT': { top: 16, right: 16 },
- 'TOP-LEFT': { top: 16, left: 16 },
- 'BOTTOM-LEFT': { bottom: 60, left: 16 },
- 'BOTTOM-RIGHT': { bottom: 60, right: 16 },
+const placementStyles: Record<
+ NonNullable,
+ React.CSSProperties
+> = {
+ "TOP-RIGHT": { top: 16, right: 16 },
+ "TOP-LEFT": { top: 16, left: 16 },
+ "BOTTOM-LEFT": { bottom: 60, left: 16 },
+ "BOTTOM-RIGHT": { bottom: 60, right: 16 },
};
-export const TimelineActionsComponent = ({timelineActions, progress, containerRef, seekTo, isVideoPlayerSmall}: TimelineActionsComponentProps) => {
- const [isOpen, setIsOpen] = useState(true)
+export const TimelineActionsComponent = ({
+ timelineActions,
+ progress,
+ containerRef,
+ seekTo,
+ isVideoPlayerSmall,
+}: TimelineActionsComponentProps) => {
+ const [isOpen, setIsOpen] = useState(true);
- const handleClick = useCallback((action: TimelineAction)=> {
- if(action?.type === 'SEEK'){
- if(!action?.seekToTime) return
- seekTo(action.seekToTime)
- } else if(action?.type === 'CUSTOM'){
- if(action.onClick){
- action.onClick()
- }
- }
- },[])
+ const handleClick = useCallback((action: TimelineAction) => {
+ if (action?.type === "SEEK") {
+ if (!action?.seekToTime) return;
+ seekTo(action.seekToTime);
+ } else if (action?.type === "CUSTOM") {
+ if (action.onClick) {
+ action.onClick();
+ }
+ }
+ }, []);
- // Find the current matching action(s)
+ // Find the current matching action(s)
const activeActions = useMemo(() => {
- return timelineActions.filter(action => {
- return progress >= action.time && progress <= action.time + action.duration;
+ return timelineActions.filter((action) => {
+ return (
+ progress >= action.time && progress <= action.time + action.duration
+ );
});
}, [timelineActions, progress]);
@@ -44,32 +54,36 @@ export const TimelineActionsComponent = ({timelineActions, progress, containerRe
if (!hasActive) return null; // Don’t render unless active
return (
<>
- {activeActions.map((action, index) => {
- const placement = (action.placement ?? 'TOP-RIGHT') as keyof typeof placementStyles;
+ {activeActions?.map((action, index) => {
+ const placement = (action.placement ??
+ "TOP-RIGHT") as keyof typeof placementStyles;
return (
-
-
- handleClick(action)}>
- {action.label}
-
-
-
- )
- } )}
+
+ handleClick(action)}
+ >
+ {action.label}
+
+
+ );
+ })}
>
- )
-}
+ );
+};
diff --git a/src/components/VideoPlayer/VideoControls.tsx b/src/components/VideoPlayer/VideoControls.tsx
index a2fa9cf..d85c86d 100644
--- a/src/components/VideoPlayer/VideoControls.tsx
+++ b/src/components/VideoPlayer/VideoControls.tsx
@@ -65,50 +65,50 @@ export const ReloadButton = ({ reloadVideo, isScreenSmall }: any) => {
);
};
-export const ProgressSlider = ({ progress, setLocalProgress, duration, playerRef, resetHideTimeout, isVideoPlayerSmall }: any) => {
+export const ProgressSlider = ({
+ progress,
+ setLocalProgress,
+ duration,
+ playerRef,
+ resetHideTimeout,
+ isVideoPlayerSmall,
+}: any) => {
const sliderRef = useRef(null);
const [isDragging, setIsDragging] = useState(false);
-const [sliderValue, setSliderValue] = useState(0); // local slider value
+ const [sliderValue, setSliderValue] = useState(0); // local slider value
const [hoverX, setHoverX] = useState(null);
const [thumbnailUrl, setThumbnailUrl] = useState(null);
const [showDuration, setShowDuration] = useState(0);
-const showTimeFunc = (val: number, clientX: number) => {
- const slider = sliderRef.current;
- if (!slider) return;
- console.log('time',val, duration)
- const percent = val / duration;
- const time = Math.min(Math.max(0, percent * duration), duration);
+ const showTimeFunc = (val: number, clientX: number) => {
+ const slider = sliderRef.current;
+ if (!slider) return;
+ const percent = val / duration;
+ const time = Math.min(Math.max(0, percent * duration), duration);
- setHoverX(clientX);
- setShowDuration(time);
+ setHoverX(clientX);
+ setShowDuration(time);
- resetHideTimeout()
- // Optionally debounce processing thumbnails
- // debounceTimeoutRef.current = setTimeout(() => {
- // debouncedExtract(time, clientX);
- // }, THUMBNAIL_DEBOUNCE);
-};
+ resetHideTimeout();
+ };
const onProgressChange = (e: any, value: number | number[]) => {
- const clientX = 'touches' in e ? e.touches[0].clientX : (e as React.MouseEvent).clientX;
-if(clientX && resetHideTimeout){
+ const clientX =
+ "touches" in e ? e.touches[0].clientX : (e as React.MouseEvent).clientX;
+ if (clientX && resetHideTimeout) {
showTimeFunc(value as number, clientX);
-
-
-}
+ }
setIsDragging(true);
setSliderValue(value as number);
};
-const onChangeCommitted = (e: any, value: number | number[]) => {
+ const onChangeCommitted = (e: any, value: number | number[]) => {
if (!playerRef.current) return;
- setSliderValue(value as number);
+ setSliderValue(value as number);
playerRef.current?.currentTime(value as number);
- setIsDragging(false);
- setLocalProgress(value)
-handleMouseLeave()
+ setIsDragging(false);
+ setLocalProgress(value);
+ handleMouseLeave();
};
-
const THUMBNAIL_DEBOUNCE = 500;
const THUMBNAIL_MIN_DIFF = 10;
@@ -128,10 +128,6 @@ handleMouseLeave()
setShowDuration(time);
if (debounceTimeoutRef.current) clearTimeout(debounceTimeoutRef.current);
-
- // debounceTimeoutRef.current = setTimeout(() => {
- // debouncedExtract(time, e.clientX);
- // }, THUMBNAIL_DEBOUNCE);
};
const handleMouseLeave = () => {
@@ -160,10 +156,8 @@ handleMouseLeave()
console.log("thumbnailUrl", thumbnailUrl, hoverX);
}
- const handleClickCapture = (e: React.MouseEvent) => {
-
- e.stopPropagation();
-
+ const handleClickCapture = (e: React.MouseEvent) => {
+ e.stopPropagation();
};
return (
@@ -190,8 +184,7 @@ handleMouseLeave()
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
onClickCapture={handleClickCapture}
- value={isDragging ? sliderValue : progress} // use local state if dragging
-
+ value={isDragging ? sliderValue : progress} // use local state if dragging
onChange={onProgressChange}
onChangeCommitted={onChangeCommitted}
min={0}
@@ -232,8 +225,6 @@ handleMouseLeave()
placement="top"
disablePortal
modifiers={[{ name: "offset", options: { offset: [-10, 0] } }]}
-
-
>
- {/*
-
-
- */}
{formatTime(showDuration)}
@@ -290,8 +259,8 @@ export const VideoTime = ({ progress, isScreenSmall, duration }: any) => {
placement="bottom"
arrow
disableHoverListener={isScreenSmall}
- disableFocusListener={isScreenSmall}
- disableTouchListener={isScreenSmall}
+ disableFocusListener={isScreenSmall}
+ disableTouchListener={isScreenSmall}
>
{
);
};
-export const VolumeControl = ({ sliderWidth, onVolumeChange, volume , isMuted, toggleMute}: any) => {
+export const VolumeControl = ({
+ sliderWidth,
+ onVolumeChange,
+ volume,
+ isMuted,
+ toggleMute,
+}: any) => {
return (
{
const [isOpen, setIsOpen] = useState(false);
const btnRef = useRef(null);
@@ -398,7 +373,7 @@ export const PlaybackRate = ({
arrow
>
-
-
>
);
};
@@ -478,136 +451,138 @@ export const FullscreenButton = ({ toggleFullscreen, isScreenSmall }: any) => {
};
interface PlayBackMenuProps {
- close: ()=> void
- isOpen: boolean
- onSelect: (speed: number)=> void;
- playbackRate: number
- isFromDrawer: boolean
+ close: () => void;
+ isOpen: boolean;
+ onSelect: (speed: number) => void;
+ playbackRate: number;
+ isFromDrawer: boolean;
}
-export const PlayBackMenu = ({close, onSelect, isOpen, playbackRate, isFromDrawer}: PlayBackMenuProps)=> {
- const theme = useTheme()
- const ref = useRef(null)
+export const PlayBackMenu = ({
+ close,
+ onSelect,
+ isOpen,
+ playbackRate,
+ isFromDrawer,
+}: PlayBackMenuProps) => {
+ const theme = useTheme();
+ const ref = useRef(null);
- useEffect(()=> {
- if(isOpen){
- ref?.current?.focus()
+ useEffect(() => {
+ if (isOpen) {
+ ref?.current?.focus();
}
- }, [isOpen])
+ }, [isOpen]);
const handleBlur = (e: React.FocusEvent) => {
- if (!e.currentTarget.contains(e.relatedTarget) && !isFromDrawer) {
- close();
- }
-};
- if(!isOpen) return null
+ if (!e.currentTarget.contains(e.relatedTarget) && !isFromDrawer) {
+ close();
+ }
+ };
+ if (!isOpen) return null;
return (
-
-
+
-
-
-
+
+
+
+
+ Playback speed
+
+
+
+
+
+ {speeds?.map((speed) => {
+ const isSelected = speed === playbackRate;
+ return (
+ {
+ onSelect(speed);
+ close();
}}
- />
-
-
-
- Playback speed
-
-
-
-
-
- {speeds?.map((speed) => {
- const isSelected = speed === playbackRate;
- return (
- {
- onSelect(speed)
- close()
- }}
- sx={{
- px: 2,
- py: 1,
- "&:hover": {
- backgroundColor: "rgba(255, 255, 255, 0.1)",
- },
- width: "100%",
- justifyContent: "space-between",
- }}
- >
- {speed}
- {isSelected ? : }
-
- );
- })}
-
+ {speed}
+ {isSelected ? : }
+
+ );
+ })}
- )
-}
\ No newline at end of file
+
+ );
+};
diff --git a/src/components/VideoPlayer/VideoControlsBar.tsx b/src/components/VideoPlayer/VideoControlsBar.tsx
index 2999c36..4766aec 100644
--- a/src/components/VideoPlayer/VideoControlsBar.tsx
+++ b/src/components/VideoPlayer/VideoControlsBar.tsx
@@ -2,8 +2,6 @@ import { Box, IconButton } from "@mui/material";
import { ControlsContainer } from "./VideoPlayer-styles";
import {
FullscreenButton,
- ObjectFitButton,
- PictureInPictureButton,
PlaybackRate,
PlayButton,
ProgressSlider,
@@ -11,42 +9,67 @@ import {
VideoTime,
VolumeControl,
} from "./VideoControls";
-import { Ref } from "react";
-import SubtitlesIcon from '@mui/icons-material/Subtitles';
+import SubtitlesIcon from "@mui/icons-material/Subtitles";
import { CustomFontTooltip } from "./CustomFontTooltip";
interface VideoControlsBarProps {
- canPlay: boolean
- isScreenSmall: boolean
- controlsHeight?: string
+ canPlay: boolean;
+ isScreenSmall: boolean;
+ controlsHeight?: string;
progress: number;
- duration: number
+ duration: number;
isPlaying: boolean;
- togglePlay: ()=> void;
- reloadVideo: ()=> void;
- volume: number
- onVolumeChange: (_: any, val: number)=> void
- toggleFullscreen: ()=> void
- extractFrames: (time: number)=> void
+ togglePlay: () => void;
+ reloadVideo: () => void;
+ volume: number;
+ onVolumeChange: (_: any, val: number) => void;
+ toggleFullscreen: () => void;
showControls: boolean;
showControlsFullScreen: boolean;
isFullScreen: boolean;
- playerRef: any
- increaseSpeed: ()=> void
- decreaseSpeed: ()=> void
- playbackRate: number
- openSubtitleManager: ()=> void
- subtitleBtnRef: any
- onSelectPlaybackRate: (rate: number)=> void;
- isMuted: boolean
- toggleMute: ()=> void
- openPlaybackMenu: ()=> void
- togglePictureInPicture: ()=> void
- isVideoPlayerSmall: boolean
- setLocalProgress: (val: number)=> void
+ playerRef: any;
+ increaseSpeed: () => void;
+ decreaseSpeed: () => void;
+ playbackRate: number;
+ openSubtitleManager: () => void;
+ subtitleBtnRef: any;
+ onSelectPlaybackRate: (rate: number) => void;
+ isMuted: boolean;
+ toggleMute: () => void;
+ openPlaybackMenu: () => void;
+ togglePictureInPicture: () => void;
+ isVideoPlayerSmall: boolean;
+ setLocalProgress: (val: number) => void;
}
-export const VideoControlsBar = ({subtitleBtnRef, setLocalProgress, showControls, playbackRate, increaseSpeed,decreaseSpeed, isFullScreen, showControlsFullScreen, reloadVideo, onVolumeChange, volume, isPlaying, canPlay, isScreenSmall, controlsHeight, playerRef, duration, progress, togglePlay, toggleFullscreen, extractFrames, openSubtitleManager, onSelectPlaybackRate, isMuted, toggleMute, openPlaybackMenu, togglePictureInPicture, isVideoPlayerSmall}: VideoControlsBarProps) => {
-
+export const VideoControlsBar = ({
+ subtitleBtnRef,
+ setLocalProgress,
+ showControls,
+ playbackRate,
+ increaseSpeed,
+ decreaseSpeed,
+ isFullScreen,
+ showControlsFullScreen,
+ reloadVideo,
+ onVolumeChange,
+ volume,
+ isPlaying,
+ canPlay,
+ isScreenSmall,
+ controlsHeight,
+ playerRef,
+ duration,
+ progress,
+ togglePlay,
+ toggleFullscreen,
+ openSubtitleManager,
+ onSelectPlaybackRate,
+ isMuted,
+ toggleMute,
+ openPlaybackMenu,
+ togglePictureInPicture,
+ isVideoPlayerSmall,
+}: VideoControlsBarProps) => {
const showMobileControls = isScreenSmall && canPlay;
const controlGroupSX = {
@@ -56,13 +79,13 @@ export const VideoControlsBar = ({subtitleBtnRef, setLocalProgress, showControls
height: controlsHeight,
};
- let additionalStyles: React.CSSProperties = {}
- if(isFullScreen && showControlsFullScreen){
+ let additionalStyles: React.CSSProperties = {};
+ if (isFullScreen && showControlsFullScreen) {
additionalStyles = {
opacity: 1,
- position: 'fixed',
- bottom: 0
- }
+ position: "fixed",
+ bottom: 0,
+ };
}
return (
@@ -70,58 +93,72 @@ export const VideoControlsBar = ({subtitleBtnRef, setLocalProgress, showControls
style={{
padding: "0px",
opacity: showControls ? 1 : 0,
- pointerEvents: showControls ? 'auto' : 'none',
- transition: 'opacity 0.4s ease-in-out',
- width: '100%'
- // ...additionalStyles
- // height: controlsHeight,
+ pointerEvents: showControls ? "auto" : "none",
+ transition: "opacity 0.4s ease-in-out",
+ width: "100%",
}}
>
- {showMobileControls ? (
- null
- ) : canPlay ? (
-
-
-
- {!isVideoPlayerSmall && (
-
-
-
-
+ {showMobileControls ? null : canPlay ? (
+
+
+ {!isVideoPlayerSmall && (
+
+
+
+
-
+
+
+
-
-
-
-
-
-
- {/* */}
-
+
+ {/* */}
+
+
-
-
-
-
- {/* */}
-
-
-
- )}
-
+
+
+
+ {/* */}
+
+
+
+ )}
) : null}
diff --git a/src/components/VideoPlayer/VideoPlayer.tsx b/src/components/VideoPlayer/VideoPlayer.tsx
index 70f2b4d..0897de5 100644
--- a/src/components/VideoPlayer/VideoPlayer.tsx
+++ b/src/components/VideoPlayer/VideoPlayer.tsx
@@ -1,9 +1,6 @@
import {
- ReactEventHandler,
- Ref,
RefObject,
useCallback,
- useContext,
useEffect,
useLayoutEffect,
useMemo,
@@ -13,33 +10,24 @@ import {
import { QortalGetMetadata } from "../../types/interfaces/resources";
import { VideoContainer, VideoElement } from "./VideoPlayer-styles";
import { useVideoPlayerHotKeys } from "./useVideoPlayerHotKeys";
-import { useProgressStore, useVideoStore } from "../../state/video";
+import {
+ useIsPlaying,
+ useProgressStore,
+ useVideoStore,
+} from "../../state/video";
import { useVideoPlayerController } from "./useVideoPlayerController";
import { LoadingVideo } from "./LoadingVideo";
import { VideoControlsBar } from "./VideoControlsBar";
import videojs from "video.js";
import "video.js/dist/video-js.css";
-import Player from "video.js/dist/types/player";
-import {
- Subtitle,
- SubtitleManager,
- SubtitleManagerProps,
- SubtitlePublishedData,
-} from "./SubtitleManager";
+import { SubtitleManager, SubtitlePublishedData } from "./SubtitleManager";
import { base64ToBlobUrl } from "../../utils/base64";
import convert from "srt-webvtt";
import { TimelineActionsComponent } from "./TimelineActionsComponent";
import { PlayBackMenu } from "./VideoControls";
import { useGlobalPlayerStore } from "../../state/pip";
-import {
- alpha,
- Box,
- ClickAwayListener,
- Drawer,
- List,
- ListItem,
-} from "@mui/material";
+import { alpha, ClickAwayListener, Drawer } from "@mui/material";
import { MobileControls } from "./MobileControls";
import { useLocation } from "react-router-dom";
@@ -84,14 +72,17 @@ export type TimelineAction =
onClick: () => void; // ✅ Required for CUSTOM
placement?: "TOP-RIGHT" | "TOP-LEFT" | "BOTTOM-LEFT" | "BOTTOM-RIGHT";
};
-interface VideoPlayerProps {
+export interface VideoPlayerProps {
qortalVideoResource: QortalGetMetadata;
- videoRef: Ref;
+ videoRef: any;
retryAttempts?: number;
poster?: string;
autoPlay?: boolean;
onEnded?: (e: React.SyntheticEvent) => void;
timelineActions?: TimelineAction[];
+ playerRef: any;
+ locationRef: RefObject;
+ videoLocationRef: RefObject;
}
const videoStyles = {
@@ -117,19 +108,20 @@ export const isTouchDevice =
export const VideoPlayer = ({
videoRef,
+ playerRef,
qortalVideoResource,
retryAttempts,
poster,
autoPlay,
onEnded,
timelineActions,
+ locationRef,
+ videoLocationRef,
}: VideoPlayerProps) => {
const containerRef = useRef(null);
const [videoObjectFit] = useState("contain");
- const [isPlaying, setIsPlaying] = useState(false);
-
+ const { isPlaying, setIsPlaying } = useIsPlaying();
const [width, setWidth] = useState(0);
- console.log("width", width);
useEffect(() => {
const observer = new ResizeObserver(([entry]) => {
setWidth(entry.contentRect.width);
@@ -145,12 +137,11 @@ export const VideoPlayer = ({
playbackRate: state.playbackSettings.playbackRate,
})
);
- const playerRef = useRef(null);
+ // const playerRef = useRef(null);
const [drawerOpenSubtitles, setDrawerOpenSubtitles] = useState(false);
const [drawerOpenPlayback, setDrawerOpenPlayback] = useState(false);
const [showControlsMobile2, setShowControlsMobile] = useState(false);
const [isPlayerInitialized, setIsPlayerInitialized] = useState(false);
- const [videoCodec, setVideoCodec] = useState(null);
const [isMuted, setIsMuted] = useState(false);
const { setProgress } = useProgressStore();
const [localProgress, setLocalProgress] = useState(0);
@@ -160,9 +151,8 @@ export const VideoPlayer = ({
const [isOpenSubtitleManage, setIsOpenSubtitleManage] = useState(false);
const subtitleBtnRef = useRef(null);
const [currentSubTrack, setCurrentSubTrack] = useState(null);
- const location = useLocation()
+ const location = useLocation();
- const locationRef = useRef(null);
const [isOpenPlaybackMenu, setIsOpenPlaybackmenu] = useState(false);
const isVideoPlayerSmall = width < 600 || isTouchDevice;
const {
@@ -176,36 +166,33 @@ export const VideoPlayer = ({
toggleObjectFit,
controlsHeight,
setProgressRelative,
- toggleAlwaysShowControls,
changeVolume,
- startedFetch,
isReady,
resourceUrl,
startPlay,
setProgressAbsolute,
- setAlwaysShowControls,
status,
percentLoaded,
showControlsFullScreen,
onSelectPlaybackRate,
seekTo,
togglePictureInPicture,
- downloadResource
+ downloadResource,
} = useVideoPlayerController({
autoPlay,
playerRef,
qortalVideoResource,
retryAttempts,
- isPlayerInitialized,
isMuted,
videoRef,
});
- const showControlsMobile = (showControlsMobile2 || !isPlaying) && isVideoPlayerSmall
+ const showControlsMobile =
+ (showControlsMobile2 || !isPlaying) && isVideoPlayerSmall;
useEffect(() => {
if (location) {
- locationRef.current = location.pathname;
+ locationRef.current = location?.pathname;
}
}, [location]);
@@ -243,10 +230,6 @@ export const VideoPlayer = ({
}
}, []);
- // const exitFullscreen = useCallback(() => {
- // document?.exitFullscreen();
- // }, [isFullscreen]);
-
const exitFullscreen = useCallback(async () => {
try {
if (document.fullscreenElement) {
@@ -275,8 +258,8 @@ export const VideoPlayer = ({
}, [isFullscreen]);
const toggleFullscreen = useCallback(() => {
- setShowControls(false)
- setShowControlsMobile(false)
+ setShowControls(false);
+ setShowControlsMobile(false);
isFullscreen ? exitFullscreen() : enterFullscreen();
}, [isFullscreen]);
@@ -286,13 +269,11 @@ export const VideoPlayer = ({
togglePlay,
setProgressRelative,
toggleObjectFit,
- toggleAlwaysShowControls,
increaseSpeed,
decreaseSpeed,
changeVolume,
toggleMute,
setProgressAbsolute,
- setAlwaysShowControls,
toggleFullscreen,
}),
[
@@ -300,13 +281,11 @@ export const VideoPlayer = ({
togglePlay,
setProgressRelative,
toggleObjectFit,
- toggleAlwaysShowControls,
increaseSpeed,
decreaseSpeed,
changeVolume,
toggleMute,
setProgressAbsolute,
- setAlwaysShowControls,
toggleFullscreen,
]
);
@@ -318,7 +297,7 @@ export const VideoPlayer = ({
const openSubtitleManager = useCallback(() => {
if (isVideoPlayerSmall) {
setDrawerOpenSubtitles(true);
- return
+ return;
}
setIsOpenSubtitleManage(true);
}, [isVideoPlayerSmall]);
@@ -327,7 +306,6 @@ export const VideoPlayer = ({
if (!qortalVideoResource) return null;
return `${qortalVideoResource.service}-${qortalVideoResource.name}-${qortalVideoResource.identifier}`;
}, [qortalVideoResource]);
- const videoLocationRef = useRef(null);
useEffect(() => {
videoLocationRef.current = videoLocation;
}, [videoLocation]);
@@ -352,15 +330,6 @@ export const VideoPlayer = ({
}
}
}, [videoLocation]);
- // useEffect(() => {
- // const ref = videoRef as React.RefObject;
- // if (!ref.current) return;
- // if (ref.current) {
- // ref.current.volume = volume;
- // }
- // // Only run on mount
- // // eslint-disable-next-line react-hooks/exhaustive-deps
- // }, []);
const onPlay = useCallback(() => {
setIsPlaying(true);
@@ -385,7 +354,6 @@ export const VideoPlayer = ({
const videoStylesContainer = useMemo(() => {
return {
cursor: "auto",
- // aspectRatio: "16 / 9",
...videoStyles?.videoContainer,
};
}, [showControls, isVideoPlayerSmall]);
@@ -432,37 +400,6 @@ export const VideoPlayer = ({
};
}, [isPlayerInitialized]);
- const canvasRef = useRef(null);
- const videoRefForCanvas = useRef(null);
- const extractFrames = useCallback((time: number): void => {
- // const video = videoRefForCanvas?.current;
- // const canvas: any = canvasRef.current;
- // if (!video || !canvas) return null;
- // // Avoid unnecessary resize if already correct
- // if (canvas.width !== video.videoWidth || canvas.height !== video.videoHeight) {
- // canvas.width = video.videoWidth;
- // canvas.height = video.videoHeight;
- // }
- // const context = canvas.getContext("2d");
- // if (!context) return null;
- // // If video is already near the correct time, don't seek again
- // const threshold = 0.01; // 10ms threshold
- // if (Math.abs(video.currentTime - time) > threshold) {
- // await new Promise((resolve) => {
- // const onSeeked = () => resolve();
- // video.addEventListener("seeked", onSeeked, { once: true });
- // video.currentTime = time;
- // });
- // }
- // context.drawImage(video, 0, 0, canvas.width, canvas.height);
- // // Use a faster method for image export (optional tradeoff)
- // const blob = await new Promise((resolve) => {
- // canvas.toBlob((blob: any) => resolve(blob), "image/webp", 0.7);
- // });
- // if (!blob) return null;
- // return URL.createObjectURL(blob);
- }, []);
-
const hideTimeout = useRef(null);
const resetHideTimer = () => {
@@ -476,7 +413,6 @@ export const VideoPlayer = ({
const handleMouseMove = () => {
if (isVideoPlayerSmall) return;
- console.log('going 222')
resetHideTimer();
};
@@ -591,24 +527,6 @@ export const VideoPlayer = ({
true
);
- // Remove all existing remote text tracks
- // try {
- // const remoteTracks = playerRef.current?.remoteTextTracks()?.tracks_
- // if (remoteTracks && remoteTracks?.length) {
- // const toRemove: TextTrack[] = [];
- // for (let i = 0; i < remoteTracks.length; i++) {
- // const track = remoteTracks[i];
- // toRemove.push(track);
- // }
- // toRemove.forEach((track) => {
- // console.log('removing track')
- // playerRef.current?.removeRemoteTextTrack(track);
- // });
- // }
- // } catch (error) {
- // console.log('error2', error)
- // }
-
await new Promise((res) => {
setTimeout(() => {
res(null);
@@ -660,12 +578,10 @@ export const VideoPlayer = ({
return;
const resource = JSON.parse(videoLocactionStringified);
- let canceled = false;
try {
const setupPlayer = async () => {
const type = await getVideoMimeTypeFromUrl(resource);
- if (canceled) return;
const options = {
autoplay: true,
@@ -723,7 +639,6 @@ export const VideoPlayer = ({
setCurrentSubTrack(activeTrack.language || activeTrack.srclang);
} else {
setCurrentSubTrack(null);
- console.log("No subtitle is currently showing");
}
};
@@ -736,7 +651,6 @@ export const VideoPlayer = ({
playerRef.current?.on("error", () => {
const error = playerRef.current?.error();
console.error("Video.js playback error:", error);
- // Optional: display user-friendly message
});
}
};
@@ -745,39 +659,6 @@ export const VideoPlayer = ({
} catch (error) {
console.error("useEffect start player", error);
}
- return () => {
- const video = savedVideoRef as any;
- const videoEl = video?.current!;
- const player = playerRef.current;
-
- const isPlaying = !player?.paused();
-
- if (videoEl && isPlaying && videoLocationRef.current) {
- const current = player?.currentTime?.();
- const currentSource = player?.currentType();
-
- useGlobalPlayerStore.getState().setVideoState({
- videoSrc: videoEl.src,
- currentTime: current ?? 0,
- isPlaying: true,
- mode: "floating",
- videoId: videoLocationRef.current,
- location: locationRef.current || "",
- type: currentSource || "video/mp4",
- });
- }
-
- canceled = true;
-
- if (player && typeof player.dispose === "function") {
- try {
- player.dispose();
- } catch (err) {
- console.error("Error disposing Video.js player:", err);
- }
- playerRef.current = null;
- }
- };
}, [isReady, resourceUrl, startPlay, poster, videoLocactionStringified]);
useEffect(() => {
@@ -815,29 +696,22 @@ export const VideoPlayer = ({
if (!container) return;
container.addEventListener("touchstart", handleInteraction);
- // container.addEventListener('mousemove', handleInteraction);
return () => {
container.removeEventListener("touchstart", handleInteraction);
- // container.removeEventListener('mousemove', handleInteraction);
};
}, []);
- const handleClickVideoElement = useCallback(()=> {
- if(isVideoPlayerSmall){
- resetHideTimeout()
- return
+ const handleClickVideoElement = useCallback(() => {
+ if (isVideoPlayerSmall) {
+ resetHideTimeout();
+ return;
}
- console.log('sup')
- togglePlay()
- }, [isVideoPlayerSmall, togglePlay])
-
- console.log("showControlsMobile", isVideoPlayerSmall);
+ togglePlay();
+ }, [isVideoPlayerSmall, togglePlay]);
return (
<>
- {/* */}
-
{!isVideoPlayerSmall && (
+ isFromDrawer={false}
+ close={closePlaybackMenu}
+ isOpen={isOpenPlaybackMenu}
+ onSelect={onSelectPlaybackRate}
+ playbackRate={playbackRate}
+ />
)}
-
{isReady && showControls && (
)}
- setDrawerOpenSubtitles(false)}>
- setDrawerOpenSubtitles(false)}>
+
-
-
-
+ }}
+ >
+
+
+
setDrawerOpenPlayback(false)}>
) => void;
+ timelineActions?: TimelineAction[];
+}
+export const VideoPlayerParent = ({
+ videoRef,
+ qortalVideoResource,
+ retryAttempts,
+ poster,
+ autoPlay,
+ onEnded,
+ timelineActions,
+}: VideoPlayerParentProps) => {
+ const playerRef = useRef(null);
+ const locationRef = useRef(null);
+ const videoLocationRef = useRef(null);
+ const { isPlaying, setIsPlaying } = useIsPlaying();
+ const isPlayingRef = useRef(false);
+ useEffect(() => {
+ isPlayingRef.current = isPlaying;
+ }, [isPlaying]);
+
+ useEffect(() => {
+ return () => {
+ const player = playerRef.current;
+
+ const isPlaying = isPlayingRef.current;
+ const currentSrc = player?.currentSrc();
+
+ if (currentSrc && isPlaying && videoLocationRef.current) {
+ const current = player?.currentTime?.();
+ const currentSource = player?.currentType();
+
+ useGlobalPlayerStore.getState().setVideoState({
+ videoSrc: currentSrc,
+ currentTime: current ?? 0,
+ isPlaying: true,
+ mode: "floating",
+ videoId: videoLocationRef.current,
+ location: locationRef.current || "",
+ type: currentSource || "video/mp4",
+ });
+ }
+ };
+ }, []);
+ useEffect(() => {
+ return () => {
+ const player = playerRef.current;
+
+ setIsPlaying(false);
+
+ if (player && typeof player.dispose === "function") {
+ try {
+ player.dispose();
+ } catch (err) {
+ console.error("Error disposing Video.js player:", err);
+ }
+ playerRef.current = null;
+ }
+ };
+ }, [
+ qortalVideoResource?.service,
+ qortalVideoResource?.name,
+ qortalVideoResource?.identifier,
+ ]);
+
+ return (
+
+ );
+};
diff --git a/src/components/VideoPlayer/useVideoPlayerController.tsx b/src/components/VideoPlayer/useVideoPlayerController.tsx
index ef78de1..da19fb8 100644
--- a/src/components/VideoPlayer/useVideoPlayerController.tsx
+++ b/src/components/VideoPlayer/useVideoPlayerController.tsx
@@ -1,14 +1,5 @@
-import {
- useState,
- useEffect,
- RefObject,
- useMemo,
- useCallback,
- Ref,
- useRef,
- useImperativeHandle,
-} from "react";
-import { useProgressStore, useVideoStore } from "../../state/video";
+import { useState, useEffect, useCallback, useRef } from "react";
+import { useVideoStore } from "../../state/video";
import { QortalGetMetadata } from "../../types/interfaces/resources";
import { useResourceStatus } from "../../hooks/useResourceStatus";
import useIdleTimeout from "../../common/useIdleTimeout";
@@ -24,81 +15,70 @@ interface UseVideoControls {
autoPlay?: boolean;
qortalVideoResource: QortalGetMetadata;
retryAttempts?: number;
- isPlayerInitialized: boolean
- isMuted: boolean
- videoRef: any
+ isMuted: boolean;
+ videoRef: any;
}
export const useVideoPlayerController = (props: UseVideoControls) => {
- const { autoPlay, videoRef , playerRef, qortalVideoResource, retryAttempts, isPlayerInitialized, isMuted } = props;
+ const {
+ autoPlay,
+ videoRef,
+ playerRef,
+ qortalVideoResource,
+ retryAttempts,
+ isMuted,
+ } = props;
const [isFullscreen, setIsFullscreen] = useState(false);
- const [showControlsFullScreen, setShowControlsFullScreen] = useState(false)
+ const [showControlsFullScreen, setShowControlsFullScreen] = useState(false);
const [videoObjectFit, setVideoObjectFit] = useState<"contain" | "fill">(
"contain"
);
- const [alwaysShowControls, setAlwaysShowControls] = useState(false);
const [startPlay, setStartPlay] = useState(false);
const [startedFetch, setStartedFetch] = useState(false);
const startedFetchRef = useRef(false);
- const { playbackSettings, setPlaybackRate, setVolume } = useVideoStore();
- const { getProgress } = useProgressStore();
+ const { playbackSettings, setPlaybackRate } = useVideoStore();
- const { isReady, resourceUrl, status, percentLoaded, downloadResource } = useResourceStatus({
- resource: !startedFetch ? null : qortalVideoResource,
- retryAttempts,
- });
-
- const idleTime = 5000; // Time in milliseconds
- useIdleTimeout({
- onIdle: () => (setShowControlsFullScreen(false)),
- onActive: () => (setShowControlsFullScreen(true)),
- idleTime,
+ const { isReady, resourceUrl, status, percentLoaded, downloadResource } =
+ useResourceStatus({
+ resource: !startedFetch ? null : qortalVideoResource,
+ retryAttempts,
});
-
- const videoLocation = useMemo(() => {
- if (!qortalVideoResource) return null;
- return `${qortalVideoResource.service}-${qortalVideoResource.name}-${qortalVideoResource.identifier}`;
- }, [qortalVideoResource]);
-
-
-
- const [playbackRate, _setLocalPlaybackRate] = useState(
- playbackSettings.playbackRate
- );
+ const idleTime = 5000; // Time in milliseconds
+ useIdleTimeout({
+ onIdle: () => setShowControlsFullScreen(false),
+ onActive: () => setShowControlsFullScreen(true),
+ idleTime,
+ });
const updatePlaybackRate = useCallback(
- (newSpeed: number) => {
- try {
- const player = playerRef.current;
- if (!player) return;
+ (newSpeed: number) => {
+ try {
+ const player = playerRef.current;
+ if (!player) return;
- if (newSpeed > maxSpeed || newSpeed < minSpeed) newSpeed = minSpeed;
-
- const clampedSpeed = Math.min(Math.max(newSpeed, minSpeed), maxSpeed);
- player.playbackRate(clampedSpeed); // ✅ Video.js API
-
- // _setLocalPlaybackRate(clampedSpeed);
- // setPlaybackRate(clampedSpeed);
- } catch (error) {
- console.error('updatePlaybackRate', error)
- }
- },
- [setPlaybackRate, _setLocalPlaybackRate, minSpeed, maxSpeed]
-);
+ if (newSpeed > maxSpeed || newSpeed < minSpeed) newSpeed = minSpeed;
+ const clampedSpeed = Math.min(Math.max(newSpeed, minSpeed), maxSpeed);
+ player.playbackRate(clampedSpeed); // ✅ Video.js API
+ } catch (error) {
+ console.error("updatePlaybackRate", error);
+ }
+ },
+ [setPlaybackRate, minSpeed, maxSpeed]
+ );
const increaseSpeed = useCallback(
(wrapOverflow = true) => {
try {
const changedSpeed = playbackSettings.playbackRate + speedChange;
- const newSpeed = wrapOverflow
- ? changedSpeed
- : Math.min(changedSpeed, maxSpeed);
- updatePlaybackRate(newSpeed);
+ const newSpeed = wrapOverflow
+ ? changedSpeed
+ : Math.min(changedSpeed, maxSpeed);
+ updatePlaybackRate(newSpeed);
} catch (error) {
- console.error('increaseSpeed', increaseSpeed)
+ console.error("increaseSpeed", increaseSpeed);
}
},
[updatePlaybackRate, playbackSettings.playbackRate]
@@ -108,10 +88,6 @@ export const useVideoPlayerController = (props: UseVideoControls) => {
updatePlaybackRate(playbackSettings.playbackRate - speedChange);
}, [updatePlaybackRate, playbackSettings.playbackRate]);
- const toggleAlwaysShowControls = useCallback(() => {
- setAlwaysShowControls((prev) => !prev);
- }, [setAlwaysShowControls]);
-
useEffect(() => {
const handleFullscreenChange = () => {
setIsFullscreen(!!document.fullscreenElement);
@@ -121,153 +97,152 @@ export const useVideoPlayerController = (props: UseVideoControls) => {
document.removeEventListener("fullscreenchange", handleFullscreenChange);
}, []);
- const onVolumeChange = useCallback(
- (_: any, value: number | number[]) => {
- try {
- const newVolume = value as number;
+ const onVolumeChange = useCallback((_: any, value: number | number[]) => {
+ try {
+ const newVolume = value as number;
const ref = playerRef as any;
if (!ref.current) return;
if (ref.current) {
playerRef.current?.volume(newVolume);
-
}
- } catch (error) {
- console.error('onVolumeChange', error)
- }
- },
- []
- );
+ } catch (error) {
+ console.error("onVolumeChange", error);
+ }
+ }, []);
const toggleMute = useCallback(() => {
try {
- const ref = playerRef as any;
- if (!ref.current) return;
-
+ const ref = playerRef as any;
+ if (!ref.current) return;
- ref.current?.muted(!isMuted)
+ ref.current?.muted(!isMuted);
} catch (error) {
- console.error('toggleMute', toggleMute)
+ console.error("toggleMute", toggleMute);
}
}, [isMuted]);
- const changeVolume = useCallback(
- (delta: number) => {
+ const changeVolume = useCallback((delta: number) => {
try {
const player = playerRef.current;
- if (!player || typeof player.volume !== 'function') return;
+ if (!player || typeof player.volume !== "function") return;
- const currentVolume = player.volume(); // Get current volume (0–1)
- let newVolume = Math.max(0, Math.min(currentVolume + delta, 1));
- newVolume = +newVolume.toFixed(2); // Round to 2 decimal places
+ const currentVolume = player.volume(); // Get current volume (0–1)
+ let newVolume = Math.max(0, Math.min(currentVolume + delta, 1));
+ newVolume = +newVolume.toFixed(2); // Round to 2 decimal places
- player.volume(newVolume); // Set new volume
- player.muted(false); // Ensure it's unmuted
+ player.volume(newVolume); // Set new volume
+ player.muted(false); // Ensure it's unmuted
} catch (error) {
- console.error('changeVolume', error)
+ console.error("changeVolume", error);
}
- },
- []
-);
+ }, []);
-
+ const setProgressRelative = useCallback((seconds: number) => {
+ try {
+ const player = playerRef.current;
+ if (
+ !player ||
+ typeof player.currentTime !== "function" ||
+ typeof player.duration !== "function"
+ )
+ return;
-const setProgressRelative = useCallback((seconds: number) => {
- try {
- const player = playerRef.current;
- if (!player || typeof player.currentTime !== 'function' || typeof player.duration !== 'function') return;
+ const current = player.currentTime();
+ const duration = player.duration() || 100;
+ const newTime = Math.max(0, Math.min(current + seconds, duration));
- const current = player.currentTime();
- const duration = player.duration() || 100;
- const newTime = Math.max(0, Math.min(current + seconds, duration));
+ player.currentTime(newTime);
+ } catch (error) {
+ console.error("setProgressRelative", error);
+ }
+ }, []);
- player.currentTime(newTime);
- } catch (error) {
- console.error('setProgressRelative', error)
- }
-}, []);
+ const setProgressAbsolute = useCallback((percent: number) => {
+ try {
+ const player = playerRef.current;
+ if (
+ !player ||
+ typeof player.duration !== "function" ||
+ typeof player.currentTime !== "function"
+ )
+ return;
+ const duration = player.duration();
+ const clampedPercent = Math.min(100, Math.max(0, percent));
+ const finalTime = (duration * clampedPercent) / 100;
- const setProgressAbsolute = useCallback((percent: number) => {
-try {
- const player = playerRef.current;
- if (!player || typeof player.duration !== 'function' || typeof player.currentTime !== 'function') return;
+ player.currentTime(finalTime);
+ } catch (error) {
+ console.error("setProgressAbsolute", error);
+ }
+ }, []);
- const duration = player.duration();
- const clampedPercent = Math.min(100, Math.max(0, percent));
- const finalTime = (duration * clampedPercent) / 100;
-
- player.currentTime(finalTime);
-} catch (error) {
- console.error('setProgressAbsolute', error)
-}
-}, []);
-
- const seekTo = useCallback((time: number) => {
-try {
- const player = playerRef.current;
- if (!player || typeof player.duration !== 'function' || typeof player.currentTime !== 'function') return;
-
- player.currentTime(time);
-} catch (error) {
- console.error('setProgressAbsolute', error)
-}
-}, []);
+ const seekTo = useCallback((time: number) => {
+ try {
+ const player = playerRef.current;
+ if (
+ !player ||
+ typeof player.duration !== "function" ||
+ typeof player.currentTime !== "function"
+ )
+ return;
+ player.currentTime(time);
+ } catch (error) {
+ console.error("setProgressAbsolute", error);
+ }
+ }, []);
const toggleObjectFit = useCallback(() => {
setVideoObjectFit(videoObjectFit === "contain" ? "fill" : "contain");
}, [setVideoObjectFit]);
-const togglePlay = useCallback(async () => {
-
-
- try {
- if (!startedFetchRef.current) {
- setStartedFetch(true);
- startedFetchRef.current = true;
- setStartPlay(true);
- return;
- }
- const player = playerRef.current;
- if (!player) return;
- if (isReady) {
- if (player.paused()) {
- try {
- await player.play();
- } catch (err) {
- console.warn('Play failed:', err);
+ const togglePlay = useCallback(async () => {
+ try {
+ if (!startedFetchRef.current) {
+ setStartedFetch(true);
+ startedFetchRef.current = true;
+ setStartPlay(true);
+ return;
}
- } else {
- player.pause();
+ const player = playerRef.current;
+ if (!player) return;
+ if (isReady) {
+ if (player.paused()) {
+ try {
+ await player.play();
+ } catch (err) {
+ console.warn("Play failed:", err);
+ }
+ } else {
+ player.pause();
+ }
+ }
+ } catch (error) {
+ console.error("togglePlay", error);
}
- }
- } catch (error) {
- console.error('togglePlay', error)
- }
-}, [setStartedFetch, isReady]);
+ }, [setStartedFetch, isReady]);
+ const reloadVideo = useCallback(async () => {
+ try {
+ const player = playerRef.current;
+ if (!player || !isReady || !resourceUrl) return;
- const reloadVideo = useCallback(async () => {
- try {
- const player = playerRef.current;
- if (!player || !isReady || !resourceUrl) return;
+ const currentTime = player.currentTime();
- const currentTime = player.currentTime();
-
- player.src({ src: resourceUrl, type: 'video/mp4' }); // Adjust type if needed
- player.load();
-
- player.ready(() => {
- player.currentTime(currentTime);
- player.play().catch((err: any) => {
- console.warn('Playback failed after reload:', err);
- });
- });
- } catch (error) {
- console.error(error)
- }
-}, [isReady, resourceUrl]);
+ player.src({ src: resourceUrl, type: "video/mp4" }); // Adjust type if needed
+ player.load();
+ player.ready(() => {
+ player.currentTime(currentTime);
+ player.play().catch((err: any) => {
+ console.warn("Playback failed after reload:", err);
+ });
+ });
+ } catch (error) {
+ console.error(error);
+ }
+ }, [isReady, resourceUrl]);
useEffect(() => {
if (autoPlay) togglePlay();
@@ -279,34 +254,25 @@ const togglePlay = useCallback(async () => {
}
}, [togglePlay, isReady]);
-// videoRef?.current?.addEventListener("enterpictureinpicture", () => {
-// setPipVideoPath(window.location.pathname);
-
-// });
-
-// // when PiP ends (and you're on the wrong page), go back
-// videoRef?.current?.addEventListener("leavepictureinpicture", () => {
-// const { pipVideoPath } = usePipStore.getState();
-// if (pipVideoPath && window.location.pathname !== pipVideoPath) {
-// navigate(pipVideoPath);
-// }
-// });
-
- const togglePictureInPicture = async () => {
+ const togglePictureInPicture = async () => {
if (!videoRef.current) return;
- const player = playerRef.current;
- if (!player || typeof player.currentTime !== 'function' || typeof player.duration !== 'function') return;
+ const player = playerRef.current;
+ if (
+ !player ||
+ typeof player.currentTime !== "function" ||
+ typeof player.duration !== "function"
+ )
+ return;
- const current = player.currentTime();
- useGlobalPlayerStore.getState().setVideoState({
- videoSrc: videoRef.current.src,
- currentTime: current,
- isPlaying: true,
- mode: 'floating', // or 'floating'
- });
+ const current = player.currentTime();
+ useGlobalPlayerStore.getState().setVideoState({
+ videoSrc: videoRef.current.src,
+ currentTime: current,
+ isPlaying: true,
+ mode: "floating", // or 'floating'
+ });
};
-
return {
reloadVideo,
togglePlay,
@@ -318,14 +284,18 @@ const togglePlay = useCallback(async () => {
toggleObjectFit,
controlsHeight,
setProgressRelative,
- toggleAlwaysShowControls,
changeVolume,
setProgressAbsolute,
- setAlwaysShowControls,
startedFetch,
isReady,
resourceUrl,
startPlay,
- status, percentLoaded, showControlsFullScreen, onSelectPlaybackRate: updatePlaybackRate, seekTo, togglePictureInPicture, downloadResource
+ status,
+ percentLoaded,
+ showControlsFullScreen,
+ onSelectPlaybackRate: updatePlaybackRate,
+ seekTo,
+ togglePictureInPicture,
+ downloadResource,
};
};
diff --git a/src/components/VideoPlayer/useVideoPlayerHotKeys.tsx b/src/components/VideoPlayer/useVideoPlayerHotKeys.tsx
index ff19e66..10914f9 100644
--- a/src/components/VideoPlayer/useVideoPlayerHotKeys.tsx
+++ b/src/components/VideoPlayer/useVideoPlayerHotKeys.tsx
@@ -3,10 +3,8 @@ import { useEffect, useCallback } from 'react';
interface UseVideoControls {
reloadVideo: () => void;
togglePlay: () => void;
- setAlwaysShowControls: React.Dispatch>;
setProgressRelative: (seconds: number) => void;
toggleObjectFit: () => void;
- toggleAlwaysShowControls: () => void;
increaseSpeed: (wrapOverflow?: boolean) => void;
decreaseSpeed: () => void;
changeVolume: (delta: number) => void;
@@ -21,7 +19,6 @@ export const useVideoPlayerHotKeys = (props: UseVideoControls) => {
togglePlay,
setProgressRelative,
toggleObjectFit,
- toggleAlwaysShowControls,
increaseSpeed,
decreaseSpeed,
changeVolume,
@@ -51,9 +48,6 @@ export const useVideoPlayerHotKeys = (props: UseVideoControls) => {
case "f":
toggleFullscreen();
break;
- case "c":
- toggleAlwaysShowControls();
- break;
case "+":
case ">":
increaseSpeed(false);
@@ -126,7 +120,7 @@ export const useVideoPlayerHotKeys = (props: UseVideoControls) => {
togglePlay,
setProgressRelative,
toggleObjectFit,
- toggleAlwaysShowControls,
+
increaseSpeed,
decreaseSpeed,
changeVolume,
diff --git a/src/context/GlobalProvider.tsx b/src/context/GlobalProvider.tsx
index 32c31d5..2995e53 100644
--- a/src/context/GlobalProvider.tsx
+++ b/src/context/GlobalProvider.tsx
@@ -58,7 +58,6 @@ export const GlobalProvider = ({
// ✅ Call hooks and pass in options dynamically
const auth = useAuth(config?.auth || {});
const isPublishing = useMultiplePublishStore((s)=> s.isPublishing);
- const videoSrc = useGlobalPlayerStore((s)=> s.videoSrc);
const appInfo = useAppInfo(config.appName, config?.publicSalt);
const lists = useResources();
const identifierOperations = useIdentifiers(
@@ -83,7 +82,7 @@ export const GlobalProvider = ({
[auth, lists, appInfo, identifierOperations, persistentOperations]
);
const { clearOldProgress } = useProgressStore();
-
+
useEffect(() => {
clearOldProgress();
}, []);
@@ -91,7 +90,10 @@ export const GlobalProvider = ({
return (
-
+
+
+
+
{isPublishing && (
diff --git a/src/index.ts b/src/index.ts
index 55d1860..05b693b 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -6,7 +6,7 @@ export { useModal } from './hooks/useModal';
export { AudioPlayerControls , OnTrackChangeMeta, AudioPlayerProps, AudioPlayerHandle} from './components/AudioPlayer/AudioPlayerControls';
export {TimelineAction} from './components/VideoPlayer/VideoPlayer'
export { useAudioPlayerHotkeys } from './components/AudioPlayer/useAudioPlayerHotkeys';
-export { VideoPlayer } from './components/VideoPlayer/VideoPlayer';
+export { VideoPlayerParent as VideoPlayer } from './components/VideoPlayer/VideoPlayerParent';
export { useListReturn } from './hooks/useListData';
import './index.css'
export { executeEvent, subscribeToEvent, unsubscribeFromEvent } from './utils/events';
diff --git a/src/state/video.ts b/src/state/video.ts
index ecf7326..814dba8 100644
--- a/src/state/video.ts
+++ b/src/state/video.ts
@@ -118,3 +118,14 @@ export const useProgressStore = create()(
}
)
);
+
+
+interface IsPlayingState {
+ isPlaying: boolean;
+ setIsPlaying: (value: boolean) => void;
+}
+
+export const useIsPlaying = create((set) => ({
+ isPlaying: false,
+ setIsPlaying: (value) => set({ isPlaying: value }),
+}));
\ No newline at end of file
diff --git a/src/utils/qortal.ts b/src/utils/qortal.ts
index da2b0cc..7802d1b 100644
--- a/src/utils/qortal.ts
+++ b/src/utils/qortal.ts
@@ -4,14 +4,19 @@ export const createAvatarLink = (qortalName: string)=> {
const removeTrailingSlash = (str: string) => str.replace(/\/$/, '');
-export const createQortalLink = (type: 'APP' | 'WEBSITE', appName: string, path: string) => {
+export const createQortalLink = (
+ type: 'APP' | 'WEBSITE',
+ appName: string,
+ path: string
+) => {
+ const encodedAppName = encodeURIComponent(appName);
+ let link = `qortal://${type}/${encodedAppName}`;
- let link = 'qortal://' + type + '/' + appName
- if(path && path.startsWith('/')){
- link = link + removeTrailingSlash(path)
- }
- if(path && !path.startsWith('/')){
- link = link + '/' + removeTrailingSlash(path)
- }
- return link
- };
\ No newline at end of file
+ if (path) {
+ link += path.startsWith('/')
+ ? removeTrailingSlash(path)
+ : '/' + removeTrailingSlash(path);
+ }
+
+ return link;
+};
\ No newline at end of file