mirror of
https://github.com/Qortal/qapp-core.git
synced 2025-07-13 20:51:21 +00:00
fixed global player for mobile
This commit is contained in:
parent
4847b4a002
commit
b01232b39f
202
src/components/VideoPlayer/MobileControls.tsx
Normal file
202
src/components/VideoPlayer/MobileControls.tsx
Normal file
@ -0,0 +1,202 @@
|
||||
import { alpha, Box, IconButton } from "@mui/material";
|
||||
import React from "react";
|
||||
import { ProgressSlider } from "./VideoControls";
|
||||
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
|
||||
import PauseIcon from "@mui/icons-material/Pause";
|
||||
import SubtitlesIcon from "@mui/icons-material/Subtitles";
|
||||
import SlowMotionVideoIcon from "@mui/icons-material/SlowMotionVideo";
|
||||
import Fullscreen from "@mui/icons-material/Fullscreen";
|
||||
import Forward10Icon from "@mui/icons-material/Forward10";
|
||||
import Replay10Icon from "@mui/icons-material/Replay10";
|
||||
|
||||
interface MobileControlsProps {
|
||||
showControlsMobile: boolean;
|
||||
progress: number;
|
||||
duration: number;
|
||||
playerRef: any;
|
||||
setShowControlsMobile: (val: boolean) => void;
|
||||
isPlaying: boolean;
|
||||
togglePlay: () => void;
|
||||
openSubtitleManager: () => void;
|
||||
openPlaybackMenu: () => void;
|
||||
toggleFullscreen: () => void;
|
||||
setProgressRelative: (val: number) => void;
|
||||
}
|
||||
export const MobileControls = ({
|
||||
showControlsMobile,
|
||||
togglePlay,
|
||||
isPlaying,
|
||||
setShowControlsMobile,
|
||||
playerRef,
|
||||
progress,
|
||||
duration,
|
||||
openSubtitleManager,
|
||||
openPlaybackMenu,
|
||||
toggleFullscreen,
|
||||
setProgressRelative,
|
||||
}: MobileControlsProps) => {
|
||||
return (
|
||||
<Box
|
||||
onClick={() => setShowControlsMobile(false)}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
display: showControlsMobile ? "block" : "none",
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
left: 0,
|
||||
zIndex: 1,
|
||||
background: "rgba(0,0,0,.5)",
|
||||
opacity: 1,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: "10px",
|
||||
right: "10px",
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
openSubtitleManager();
|
||||
}}
|
||||
>
|
||||
<SubtitlesIcon
|
||||
sx={{
|
||||
fontSize: "24px",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
<IconButton
|
||||
sx={{
|
||||
fontSize: "24px",
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
openPlaybackMenu();
|
||||
}}
|
||||
>
|
||||
<SlowMotionVideoIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
gap: "50px",
|
||||
display: 'flex',
|
||||
alignItems: 'center'
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
sx={{
|
||||
opacity: 1,
|
||||
zIndex: 2,
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setProgressRelative(-10);
|
||||
}}
|
||||
>
|
||||
<Replay10Icon
|
||||
sx={{
|
||||
fontSize: "36px",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
{isPlaying && (
|
||||
<IconButton
|
||||
sx={{
|
||||
opacity: 1,
|
||||
zIndex: 2,
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
togglePlay();
|
||||
}}
|
||||
>
|
||||
<PauseIcon
|
||||
sx={{
|
||||
fontSize: "36px",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
)}
|
||||
{!isPlaying && (
|
||||
<IconButton
|
||||
sx={{
|
||||
opacity: 1,
|
||||
zIndex: 2,
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
togglePlay();
|
||||
}}
|
||||
>
|
||||
<PlayArrowIcon
|
||||
sx={{
|
||||
fontSize: "36px",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
)}
|
||||
<IconButton
|
||||
sx={{
|
||||
opacity: 1,
|
||||
zIndex: 2,
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setProgressRelative(10);
|
||||
}}
|
||||
>
|
||||
<Forward10Icon
|
||||
sx={{
|
||||
fontSize: "36px",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
bottom: "20px",
|
||||
right: "10px",
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
sx={{
|
||||
fontSize: "24px",
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
toggleFullscreen();
|
||||
}}
|
||||
>
|
||||
<Fullscreen />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
}}
|
||||
>
|
||||
<ProgressSlider
|
||||
playerRef={playerRef}
|
||||
progress={progress}
|
||||
duration={duration}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
@ -72,6 +72,8 @@ export interface SubtitleManagerProps {
|
||||
onSelect: (subtitle: SubtitlePublishedData) => void;
|
||||
subtitleBtnRef: any;
|
||||
currentSubTrack: null | string
|
||||
setDrawerOpenSubtitles: (val: boolean)=> void
|
||||
isFromDrawer: boolean
|
||||
}
|
||||
export interface Subtitle {
|
||||
language: string | null;
|
||||
@ -108,6 +110,8 @@ const SubtitleManagerComponent = ({
|
||||
onSelect,
|
||||
subtitleBtnRef,
|
||||
currentSubTrack,
|
||||
setDrawerOpenSubtitles,
|
||||
isFromDrawer = false
|
||||
}: SubtitleManagerProps) => {
|
||||
const [mode, setMode] = useState(1);
|
||||
const [isOpenPublish, setIsOpenPublish] = useState(false);
|
||||
@ -177,8 +181,11 @@ const SubtitleManagerComponent = ({
|
||||
}
|
||||
}, [open])
|
||||
|
||||
console.log('isFromDrawer', )
|
||||
|
||||
const handleBlur = (e: React.FocusEvent) => {
|
||||
if (!e.currentTarget.contains(e.relatedTarget) && !isOpenPublish) {
|
||||
if (!e.currentTarget.contains(e.relatedTarget) && !isOpenPublish && !isFromDrawer && open) {
|
||||
console.log('hello close')
|
||||
close();
|
||||
setIsOpenPublish(false)
|
||||
}
|
||||
@ -262,13 +269,13 @@ const SubtitleManagerComponent = ({
|
||||
|
||||
sx={
|
||||
{
|
||||
position: 'absolute',
|
||||
bottom: 60,
|
||||
right: 5,
|
||||
position: isFromDrawer ? 'relative' : 'absolute',
|
||||
bottom: isFromDrawer ? 'unset' : 60,
|
||||
right: isFromDrawer ? 'unset' : 5,
|
||||
color: "white",
|
||||
opacity: 0.9,
|
||||
borderRadius: 2,
|
||||
boxShadow: 5,
|
||||
boxShadow: isFromDrawer ? 'unset' : 5,
|
||||
p: 1,
|
||||
minWidth: 225,
|
||||
height: 300,
|
||||
@ -387,30 +394,7 @@ const SubtitleManagerComponent = ({
|
||||
Load community subs
|
||||
</Button>
|
||||
</Box>
|
||||
{/* <Box>
|
||||
{[
|
||||
'Ambient mode',
|
||||
'Annotations',
|
||||
'Subtitles/CC',
|
||||
'Sleep timer',
|
||||
'Playback speed',
|
||||
'Quality',
|
||||
].map((label) => (
|
||||
<Typography
|
||||
key={label}
|
||||
sx={{
|
||||
px: 2,
|
||||
py: 1,
|
||||
'&:hover': {
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
||||
cursor: 'pointer',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Typography>
|
||||
))}
|
||||
</Box> */}
|
||||
|
||||
|
||||
</Box>
|
||||
<PublishSubtitles
|
||||
@ -421,70 +405,10 @@ const SubtitleManagerComponent = ({
|
||||
/>
|
||||
|
||||
</>
|
||||
// <Dialog
|
||||
// open={!!open}
|
||||
// fullWidth={true}
|
||||
// maxWidth={"md"}
|
||||
// sx={{
|
||||
// zIndex: 999990,
|
||||
// }}
|
||||
// slotProps={{
|
||||
// paper: {
|
||||
// elevation: 0,
|
||||
// },
|
||||
// }}
|
||||
// >
|
||||
// <DialogTitle>Subtitles</DialogTitle>
|
||||
// <IconButton
|
||||
// aria-label="close"
|
||||
// onClick={handleClose}
|
||||
// sx={(theme) => ({
|
||||
// position: "absolute",
|
||||
// right: 8,
|
||||
// top: 8,
|
||||
// })}
|
||||
// >
|
||||
// <CloseIcon />
|
||||
// </IconButton>
|
||||
// <Button onClick={() => setMode(5)}>New subtitles</Button>
|
||||
// {mode === 1 && (
|
||||
// <PublisherSubtitles
|
||||
// subtitles={subtitles}
|
||||
// publisherName={qortalMetadata.name}
|
||||
// setMode={setMode}
|
||||
// onSelect={onSelect}
|
||||
// />
|
||||
// )}
|
||||
// {mode === 5 && <PublishSubtitles publishHandler={publishHandler} />}
|
||||
// {/* {mode === 2 && (
|
||||
// <CommunitySubtitles
|
||||
// link={open?.link}
|
||||
// name={open?.name}
|
||||
// mode={mode}
|
||||
// setMode={setMode}
|
||||
// username={username}
|
||||
// category={open?.category}
|
||||
// rootName={open?.rootName}
|
||||
// />
|
||||
// )}
|
||||
|
||||
// {mode === 4 && (
|
||||
// <MySubtitles
|
||||
// link={open?.link}
|
||||
// name={open?.name}
|
||||
// mode={mode}
|
||||
// setMode={setMode}
|
||||
// username={username}
|
||||
// title={title}
|
||||
// description={description}
|
||||
// setDescription={setDescription}
|
||||
// setTitle={setTitle}
|
||||
// />
|
||||
// )} */}
|
||||
// </Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
interface PublisherSubtitlesProps {
|
||||
publisherName: string;
|
||||
subtitles: any[];
|
||||
|
@ -71,7 +71,7 @@ export const ProgressSlider = ({ progress, duration, playerRef }: any) => {
|
||||
const [hoverX, setHoverX] = useState<number | null>(null);
|
||||
const [thumbnailUrl, setThumbnailUrl] = useState<string | null>(null);
|
||||
const [showDuration, setShowDuration] = useState(0);
|
||||
const onProgressChange = (_: any, value: number | number[]) => {
|
||||
const onProgressChange = (e: any, value: number | number[]) => {
|
||||
if (!playerRef.current) return;
|
||||
|
||||
playerRef.current?.currentTime(value as number);
|
||||
@ -128,6 +128,11 @@ export const ProgressSlider = ({ progress, duration, playerRef }: any) => {
|
||||
console.log("thumbnailUrl", thumbnailUrl, hoverX);
|
||||
}
|
||||
|
||||
const handleClickCapture = (e: React.MouseEvent) => {
|
||||
|
||||
e.stopPropagation();
|
||||
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
@ -152,6 +157,7 @@ export const ProgressSlider = ({ progress, duration, playerRef }: any) => {
|
||||
ref={sliderRef}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
onClickCapture={handleClickCapture}
|
||||
value={progress}
|
||||
onChange={onProgressChange}
|
||||
min={0}
|
||||
@ -439,8 +445,9 @@ interface PlayBackMenuProps {
|
||||
isOpen: boolean
|
||||
onSelect: (speed: number)=> void;
|
||||
playbackRate: number
|
||||
isFromDrawer: boolean
|
||||
}
|
||||
export const PlayBackMenu = ({close, onSelect, isOpen, playbackRate}: PlayBackMenuProps)=> {
|
||||
export const PlayBackMenu = ({close, onSelect, isOpen, playbackRate, isFromDrawer}: PlayBackMenuProps)=> {
|
||||
const theme = useTheme()
|
||||
const ref = useRef<any>(null)
|
||||
|
||||
@ -451,7 +458,7 @@ export const PlayBackMenu = ({close, onSelect, isOpen, playbackRate}: PlayBackMe
|
||||
}, [isOpen])
|
||||
|
||||
const handleBlur = (e: React.FocusEvent) => {
|
||||
if (!e.currentTarget.contains(e.relatedTarget)) {
|
||||
if (!e.currentTarget.contains(e.relatedTarget) && !isFromDrawer) {
|
||||
close();
|
||||
}
|
||||
};
|
||||
@ -466,13 +473,13 @@ export const PlayBackMenu = ({close, onSelect, isOpen, playbackRate}: PlayBackMe
|
||||
|
||||
sx={
|
||||
{
|
||||
position: 'absolute',
|
||||
bottom: 60,
|
||||
right: 5,
|
||||
position: isFromDrawer ? 'relative' : 'absolute',
|
||||
bottom: isFromDrawer ? 'relative' : 60,
|
||||
right:isFromDrawer ? 'relative' : 5,
|
||||
color: "white",
|
||||
opacity: 0.9,
|
||||
borderRadius: 2,
|
||||
boxShadow: 5,
|
||||
boxShadow: isFromDrawer ? 'relative' : 5,
|
||||
p: 1,
|
||||
minWidth: 225,
|
||||
height: 300,
|
||||
|
@ -42,9 +42,10 @@ interface VideoControlsBarProps {
|
||||
toggleMute: ()=> void
|
||||
openPlaybackMenu: ()=> void
|
||||
togglePictureInPicture: ()=> void
|
||||
isVideoPlayerSmall: boolean
|
||||
}
|
||||
|
||||
export const VideoControlsBar = ({subtitleBtnRef, 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}: VideoControlsBarProps) => {
|
||||
export const VideoControlsBar = ({subtitleBtnRef, 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) => {
|
||||
|
||||
const showMobileControls = isScreenSmall && canPlay;
|
||||
|
||||
@ -87,7 +88,8 @@ export const VideoControlsBar = ({subtitleBtnRef, showControls, playbackRate, in
|
||||
}}>
|
||||
|
||||
<ProgressSlider playerRef={playerRef} progress={progress} duration={duration} />
|
||||
<Box sx={{
|
||||
{!isVideoPlayerSmall && (
|
||||
<Box sx={{
|
||||
width: '100%',
|
||||
display: 'flex'
|
||||
}}>
|
||||
@ -117,6 +119,8 @@ export const VideoControlsBar = ({subtitleBtnRef, showControls, playbackRate, in
|
||||
<FullscreenButton toggleFullscreen={toggleFullscreen} />
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
</Box>
|
||||
) : null}
|
||||
</ControlsContainer>
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { styled } from "@mui/system";
|
||||
import { styled, Theme } from "@mui/system";
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
export const VideoContainer = styled(Box)(({ theme }) => ({
|
||||
export const VideoContainer = styled(Box, {
|
||||
shouldForwardProp: (prop) => prop !== 'isVideoPlayerSmall',
|
||||
})<{ isVideoPlayerSmall?: boolean }>(({ theme, isVideoPlayerSmall }) => ({
|
||||
position: "relative",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
@ -11,7 +13,7 @@ export const VideoContainer = styled(Box)(({ theme }) => ({
|
||||
height: "100%",
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
borderRadius: '12px',
|
||||
borderRadius: isVideoPlayerSmall ? '0px' : '12px',
|
||||
overflow: 'hidden',
|
||||
"&:focus": { outline: "none" },
|
||||
}));
|
||||
|
@ -33,6 +33,8 @@ import { TimelineActionsComponent } from "./TimelineActionsComponent";
|
||||
import { PlayBackMenu } from "./VideoControls";
|
||||
import { useGlobalPlayerStore } from "../../state/pip";
|
||||
import { LocationContext } from "../../context/GlobalProvider";
|
||||
import { alpha, Box, Drawer, List, ListItem } from "@mui/material";
|
||||
import { MobileControls } from "./MobileControls";
|
||||
|
||||
export async function srtBase64ToVttBlobUrl(
|
||||
base64Srt: string
|
||||
@ -107,6 +109,8 @@ async function getVideoMimeTypeFromUrl(
|
||||
return null;
|
||||
}
|
||||
}
|
||||
export const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
||||
|
||||
|
||||
export const VideoPlayer = ({
|
||||
videoRef,
|
||||
@ -117,9 +121,19 @@ export const VideoPlayer = ({
|
||||
onEnded,
|
||||
timelineActions
|
||||
}: VideoPlayerProps) => {
|
||||
const containerRef = useRef<RefObject<HTMLDivElement> | null>(null);
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
const [videoObjectFit] = useState<StretchVideoType>("contain");
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
|
||||
const [width, setWidth] = useState(0);
|
||||
console.log('width',width)
|
||||
useEffect(() => {
|
||||
const observer = new ResizeObserver(([entry]) => {
|
||||
setWidth(entry.contentRect.width);
|
||||
});
|
||||
if (containerRef.current) observer.observe(containerRef.current);
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
const { volume, setVolume, setPlaybackRate, playbackRate } = useVideoStore(
|
||||
(state) => ({
|
||||
volume: state.playbackSettings.volume,
|
||||
@ -129,6 +143,9 @@ export const VideoPlayer = ({
|
||||
})
|
||||
);
|
||||
const playerRef = useRef<Player | null>(null);
|
||||
const [drawerOpenSubtitles, setDrawerOpenSubtitles] = useState(false)
|
||||
const [drawerOpenPlayback, setDrawerOpenPlayback] = useState(false)
|
||||
const [showControlsMobile, setShowControlsMobile] = useState(false)
|
||||
const [isPlayerInitialized, setIsPlayerInitialized] = useState(false);
|
||||
const [videoCodec, setVideoCodec] = useState<null | false | string>(null);
|
||||
const [isMuted, setIsMuted] = useState(false);
|
||||
@ -144,6 +161,7 @@ export const VideoPlayer = ({
|
||||
|
||||
const locationRef = useRef<string | null>(null)
|
||||
const [isOpenPlaybackMenu, setIsOpenPlaybackmenu] = useState(false)
|
||||
const isVideoPlayerSmall = width < 600
|
||||
const {
|
||||
reloadVideo,
|
||||
togglePlay,
|
||||
@ -188,18 +206,71 @@ export const VideoPlayer = ({
|
||||
|
||||
const { getProgress } = useProgressStore();
|
||||
|
||||
const enterFullscreen = useCallback(() => {
|
||||
const ref = containerRef?.current as any;
|
||||
if (!ref) return;
|
||||
const enterFullscreen = useCallback(async () => {
|
||||
const ref = containerRef?.current as HTMLElement | null;
|
||||
if (!ref || document.fullscreenElement) return;
|
||||
|
||||
if (ref.requestFullscreen && !isFullscreen) {
|
||||
ref.requestFullscreen();
|
||||
try {
|
||||
// Wait for fullscreen to activate
|
||||
if (ref.requestFullscreen) {
|
||||
await ref.requestFullscreen();
|
||||
} else if ((ref as any).webkitRequestFullscreen) {
|
||||
await (ref as any).webkitRequestFullscreen(); // Safari fallback
|
||||
}
|
||||
}, []);
|
||||
|
||||
const exitFullscreen = useCallback(() => {
|
||||
document?.exitFullscreen();
|
||||
}, [isFullscreen]);
|
||||
|
||||
if (
|
||||
typeof screen.orientation !== 'undefined' &&
|
||||
'lock' in screen.orientation &&
|
||||
typeof screen.orientation.lock === 'function'
|
||||
) {
|
||||
try {
|
||||
await (screen.orientation as any).lock('landscape');
|
||||
} catch (err) {
|
||||
console.warn('Orientation lock failed:', err);
|
||||
}
|
||||
}
|
||||
await qortalRequest({
|
||||
action: 'SCREEN_ORIENTATION',
|
||||
mode: 'landscape'
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('Failed to enter fullscreen or lock orientation:', err);
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
// const exitFullscreen = useCallback(() => {
|
||||
// document?.exitFullscreen();
|
||||
// }, [isFullscreen]);
|
||||
|
||||
const exitFullscreen = useCallback(async () => {
|
||||
try {
|
||||
if (document.fullscreenElement) {
|
||||
await document.exitFullscreen();
|
||||
}
|
||||
|
||||
|
||||
if (
|
||||
typeof screen.orientation !== 'undefined' &&
|
||||
'lock' in screen.orientation &&
|
||||
typeof screen.orientation.lock === 'function'
|
||||
) {
|
||||
try {
|
||||
// Attempt to reset by locking to 'portrait' or 'any' (if supported)
|
||||
await screen.orientation.lock('portrait'); // or 'any' if supported
|
||||
} catch (err) {
|
||||
console.warn('Orientation lock failed:', err);
|
||||
}
|
||||
}
|
||||
await qortalRequest({
|
||||
action: 'SCREEN_ORIENTATION',
|
||||
mode: 'portrait'
|
||||
})
|
||||
} catch (err) {
|
||||
console.warn('Error exiting fullscreen or unlocking orientation:', err);
|
||||
}
|
||||
}, [isFullscreen]);
|
||||
|
||||
const toggleFullscreen = useCallback(() => {
|
||||
isFullscreen ? exitFullscreen() : enterFullscreen();
|
||||
@ -239,10 +310,14 @@ export const VideoPlayer = ({
|
||||
|
||||
const closeSubtitleManager = useCallback(() => {
|
||||
setIsOpenSubtitleManage(false);
|
||||
setDrawerOpenSubtitles(false)
|
||||
}, []);
|
||||
const openSubtitleManager = useCallback(() => {
|
||||
if(isVideoPlayerSmall){
|
||||
setDrawerOpenSubtitles(true)
|
||||
}
|
||||
setIsOpenSubtitleManage(true);
|
||||
}, []);
|
||||
}, [isVideoPlayerSmall]);
|
||||
|
||||
const videoLocation = useMemo(() => {
|
||||
if (!qortalVideoResource) return null;
|
||||
@ -388,6 +463,7 @@ const videoLocationRef = useRef< null | string>(null)
|
||||
const hideTimeout = useRef<any>(null);
|
||||
|
||||
const resetHideTimer = () => {
|
||||
if(isTouchDevice) return
|
||||
setShowControls(true);
|
||||
if (hideTimeout.current) clearTimeout(hideTimeout.current);
|
||||
hideTimeout.current = setTimeout(() => {
|
||||
@ -396,17 +472,24 @@ const videoLocationRef = useRef< null | string>(null)
|
||||
};
|
||||
|
||||
const handleMouseMove = () => {
|
||||
if(isTouchDevice) return
|
||||
resetHideTimer();
|
||||
};
|
||||
|
||||
const closePlaybackMenu = useCallback(()=> {
|
||||
setIsOpenPlaybackmenu(false)
|
||||
setDrawerOpenPlayback(false)
|
||||
}, [])
|
||||
const openPlaybackMenu = useCallback(()=> {
|
||||
if(isVideoPlayerSmall){
|
||||
setDrawerOpenPlayback(true)
|
||||
return
|
||||
}
|
||||
setIsOpenPlaybackmenu(true)
|
||||
}, [])
|
||||
}, [isVideoPlayerSmall])
|
||||
|
||||
useEffect(() => {
|
||||
if(isTouchDevice) return
|
||||
resetHideTimer(); // initial show
|
||||
return () => {
|
||||
if (hideTimeout.current) clearTimeout(hideTimeout.current);
|
||||
@ -717,6 +800,33 @@ savedVideoRef.current = video.current;
|
||||
player.off("ratechange", handleRateChange);
|
||||
};
|
||||
}, [isPlayerInitialized]);
|
||||
const hideTimeoutRef = useRef<number | null>(null);
|
||||
|
||||
|
||||
const resetHideTimeout = () => {
|
||||
setShowControlsMobile(true);
|
||||
if (hideTimeoutRef.current) clearTimeout(hideTimeoutRef.current);
|
||||
hideTimeoutRef.current = setTimeout(() => {
|
||||
setShowControlsMobile(false);
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleInteraction = () => resetHideTimeout();
|
||||
|
||||
const container = containerRef.current;
|
||||
if (!container) return;
|
||||
|
||||
container.addEventListener('touchstart', handleInteraction);
|
||||
// container.addEventListener('mousemove', handleInteraction);
|
||||
|
||||
return () => {
|
||||
container.removeEventListener('touchstart', handleInteraction);
|
||||
// container.removeEventListener('mousemove', handleInteraction);
|
||||
};
|
||||
}, []);
|
||||
|
||||
console.log('showControlsMobile', showControlsMobile)
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -728,6 +838,7 @@ savedVideoRef.current = video.current;
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
ref={containerRef}
|
||||
isVideoPlayerSmall={isVideoPlayerSmall}
|
||||
>
|
||||
<LoadingVideo
|
||||
togglePlay={togglePlay}
|
||||
@ -754,11 +865,11 @@ savedVideoRef.current = video.current;
|
||||
onVolumeChange={onVolumeChangeHandler}
|
||||
controls={false}
|
||||
/>
|
||||
<PlayBackMenu close={closePlaybackMenu} isOpen={isOpenPlaybackMenu} onSelect={onSelectPlaybackRate} playbackRate={playbackRate} />
|
||||
{/* <canvas ref={canvasRef} style={{ display: "none" }}></canvas> */}
|
||||
<PlayBackMenu isFromDrawer={false} close={closePlaybackMenu} isOpen={isOpenPlaybackMenu} onSelect={onSelectPlaybackRate} playbackRate={playbackRate} />
|
||||
|
||||
{isReady && (
|
||||
{isReady && !showControlsMobile && (
|
||||
<VideoControlsBar
|
||||
isVideoPlayerSmall={isVideoPlayerSmall}
|
||||
subtitleBtnRef={subtitleBtnRef}
|
||||
playbackRate={playbackRate}
|
||||
increaseSpeed={hotkeyHandlers.increaseSpeed}
|
||||
@ -790,15 +901,73 @@ savedVideoRef.current = video.current;
|
||||
{timelineActions && Array.isArray(timelineActions) && (
|
||||
<TimelineActionsComponent seekTo={seekTo} containerRef={containerRef} progress={localProgress} timelineActions={timelineActions}/>
|
||||
)}
|
||||
<SubtitleManager
|
||||
{showControlsMobile && (
|
||||
<MobileControls setProgressRelative={setProgressRelative} toggleFullscreen={toggleFullscreen} openPlaybackMenu={openPlaybackMenu} openSubtitleManager={openSubtitleManager} togglePlay={togglePlay} isPlaying={isPlaying} setShowControlsMobile={setShowControlsMobile} duration={duration}
|
||||
progress={localProgress} playerRef={playerRef} showControlsMobile={showControlsMobile} />
|
||||
)}
|
||||
|
||||
|
||||
{!isVideoPlayerSmall && (
|
||||
<SubtitleManager
|
||||
subtitleBtnRef={subtitleBtnRef}
|
||||
close={closeSubtitleManager}
|
||||
open={isOpenSubtitleManage}
|
||||
qortalMetadata={qortalVideoResource}
|
||||
onSelect={onSelectSubtitle}
|
||||
currentSubTrack={currentSubTrack}
|
||||
setDrawerOpenSubtitles={setDrawerOpenSubtitles}
|
||||
isFromDrawer={false}
|
||||
/>
|
||||
)}
|
||||
|
||||
</VideoContainer>
|
||||
<Drawer anchor="bottom" open={drawerOpenSubtitles} onClose={() => setDrawerOpenSubtitles(false)} sx={{
|
||||
|
||||
}} slotProps={{
|
||||
paper: {
|
||||
sx: {
|
||||
backgroundColor: alpha("#181818", 0.98),
|
||||
borderRadius: 2,
|
||||
width: '90%',
|
||||
margin: '0 auto',
|
||||
p: 1,
|
||||
backgroundImage: 'none',
|
||||
mb: 1
|
||||
},
|
||||
}
|
||||
}}>
|
||||
|
||||
<SubtitleManager
|
||||
subtitleBtnRef={subtitleBtnRef}
|
||||
close={closeSubtitleManager}
|
||||
open={true}
|
||||
qortalMetadata={qortalVideoResource}
|
||||
onSelect={onSelectSubtitle}
|
||||
currentSubTrack={currentSubTrack}
|
||||
setDrawerOpenSubtitles={setDrawerOpenSubtitles}
|
||||
isFromDrawer={true}
|
||||
/>
|
||||
|
||||
</Drawer>
|
||||
<Drawer anchor="bottom" open={drawerOpenPlayback} onClose={() => setDrawerOpenPlayback(false)} sx={{
|
||||
|
||||
}} slotProps={{
|
||||
paper: {
|
||||
sx: {
|
||||
backgroundColor: alpha("#181818", 0.98),
|
||||
borderRadius: 2,
|
||||
width: '90%',
|
||||
margin: '0 auto',
|
||||
p: 1,
|
||||
backgroundImage: 'none',
|
||||
mb: 1
|
||||
},
|
||||
}
|
||||
}}>
|
||||
|
||||
<PlayBackMenu isFromDrawer close={closePlaybackMenu} isOpen={true} onSelect={onSelectPlaybackRate} playbackRate={playbackRate} />
|
||||
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -18,6 +18,7 @@ import { GlobalPipPlayer } from "../hooks/useGlobalPipPlayer";
|
||||
import { Location, NavigateFunction } from "react-router-dom";
|
||||
import { MultiPublishDialog } from "../components/MultiPublish/MultiPublishDialog";
|
||||
import { useMultiplePublishStore } from "../state/multiplePublish";
|
||||
import { useGlobalPlayerStore } from "../state/pip";
|
||||
|
||||
// ✅ Define Global Context Type
|
||||
interface GlobalContextType {
|
||||
@ -62,7 +63,7 @@ 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(
|
||||
@ -97,7 +98,8 @@ export const GlobalProvider = ({
|
||||
<LocationContext.Provider value={location}>
|
||||
|
||||
<GlobalContext.Provider value={contextValue}>
|
||||
<GlobalPipPlayer />
|
||||
<GlobalPipPlayer />
|
||||
|
||||
{isPublishing && (
|
||||
<MultiPublishDialog />
|
||||
)}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { AddForeignServerQortalRequest, AddListItemsQortalRequest, BuyNameQortalRequest, CancelSellNameQortalRequest, CancelTradeSellOrderQortalRequest, CreatePollQortalRequest, CreateTradeBuyOrderQortalRequest, CreateTradeSellOrderQortalRequest, DecryptDataQortalRequest, DecryptDataWithSharingKeyQortalRequest, DecryptQortalGroupDataQortalRequest, DeleteHostedDataQortalRequest, DeleteListItemQortalRequest, EncryptDataQortalRequest, EncryptDataWithSharingKeyQortalRequest, EncryptQortalGroupDataQortalRequest, FetchQdnResourceQortalRequest, GetAccountDataQortalRequest, GetAccountNamesQortalRequest, GetBalanceQortalRequest, GetCrosschainServerInfoQortalRequest, GetDaySummaryQortalRequest, GetForeignFeeQortalRequest, GetHostedDataQortalRequest, GetListItemsQortalRequest, GetNameDataQortalRequest, GetPriceQortalRequest, GetQdnResourceMetadataQortalRequest, GetQdnResourcePropertiesQortalRequest, GetQdnResourceStatusQortalRequest, GetQdnResourceUrlQortalRequest, GetServerConnectionHistoryQortalRequest, GetTxActivitySummaryQortalRequest, GetUserAccountQortalRequest, GetUserWalletInfoQortalRequest, GetUserWalletQortalRequest, GetWalletBalanceQortalRequest, LinkToQdnResourceQortalRequest, ListQdnResourcesQortalRequest, PublishMultipleQdnResourcesQortalRequest, PublishQdnResourceQortalRequest, RegisterNameQortalRequest, RemoveForeignServerQortalRequest, SearchNamesQortalRequest, SearchQdnResourcesQortalRequest, SellNameQortalRequest, SendCoinQortalRequest, SetCurrentForeignServerQortalRequest, UpdateForeignFeeQortalRequest, UpdateNameQortalRequest, VoteOnPollQortalRequest, SendChatMessageQortalRequest, SearchChatMessagesQortalRequest, JoinGroupQortalRequest, AddGroupAdminQortalRequest, UpdateGroupQortalRequest, ListGroupsQortalRequest, CreateGroupQortalRequest, RemoveGroupAdminQortalRequest, BanFromGroupQortalRequest, CancelGroupBanQortalRequest, KickFromGroupQortalRequest, InviteToGroupQortalRequest, CancelGroupInviteQortalRequest, LeaveGroupQortalRequest, DeployAtQortalRequest, GetAtQortalRequest, GetAtDataQortalRequest, ListAtsQortalRequest, FetchBlockQortalRequest, FetchBlockRangeQortalRequest, SearchTransactionsQortalRequest, IsUsingPublicNodeQortalRequest, AdminActionQortalRequest, OpenNewTabQortalRequest, ShowActionsQortalRequest, SignTransactionQortalRequest, CreateAndCopyEmbedLinkQortalRequest, TransferAssetQortalRequest, ShowPdfReaderQortalRequest, SaveFileQortalRequest, GetPrimaryNameQortalRequest, } from "./types/qortalRequests/interfaces"
|
||||
import { AddForeignServerQortalRequest, AddListItemsQortalRequest, BuyNameQortalRequest, CancelSellNameQortalRequest, CancelTradeSellOrderQortalRequest, CreatePollQortalRequest, CreateTradeBuyOrderQortalRequest, CreateTradeSellOrderQortalRequest, DecryptDataQortalRequest, DecryptDataWithSharingKeyQortalRequest, DecryptQortalGroupDataQortalRequest, DeleteHostedDataQortalRequest, DeleteListItemQortalRequest, EncryptDataQortalRequest, EncryptDataWithSharingKeyQortalRequest, EncryptQortalGroupDataQortalRequest, FetchQdnResourceQortalRequest, GetAccountDataQortalRequest, GetAccountNamesQortalRequest, GetBalanceQortalRequest, GetCrosschainServerInfoQortalRequest, GetDaySummaryQortalRequest, GetForeignFeeQortalRequest, GetHostedDataQortalRequest, GetListItemsQortalRequest, GetNameDataQortalRequest, GetPriceQortalRequest, GetQdnResourceMetadataQortalRequest, GetQdnResourcePropertiesQortalRequest, GetQdnResourceStatusQortalRequest, GetQdnResourceUrlQortalRequest, GetServerConnectionHistoryQortalRequest, GetTxActivitySummaryQortalRequest, GetUserAccountQortalRequest, GetUserWalletInfoQortalRequest, GetUserWalletQortalRequest, GetWalletBalanceQortalRequest, LinkToQdnResourceQortalRequest, ListQdnResourcesQortalRequest, PublishMultipleQdnResourcesQortalRequest, PublishQdnResourceQortalRequest, RegisterNameQortalRequest, RemoveForeignServerQortalRequest, SearchNamesQortalRequest, SearchQdnResourcesQortalRequest, SellNameQortalRequest, SendCoinQortalRequest, SetCurrentForeignServerQortalRequest, UpdateForeignFeeQortalRequest, UpdateNameQortalRequest, VoteOnPollQortalRequest, SendChatMessageQortalRequest, SearchChatMessagesQortalRequest, JoinGroupQortalRequest, AddGroupAdminQortalRequest, UpdateGroupQortalRequest, ListGroupsQortalRequest, CreateGroupQortalRequest, RemoveGroupAdminQortalRequest, BanFromGroupQortalRequest, CancelGroupBanQortalRequest, KickFromGroupQortalRequest, InviteToGroupQortalRequest, CancelGroupInviteQortalRequest, LeaveGroupQortalRequest, DeployAtQortalRequest, GetAtQortalRequest, GetAtDataQortalRequest, ListAtsQortalRequest, FetchBlockQortalRequest, FetchBlockRangeQortalRequest, SearchTransactionsQortalRequest, IsUsingPublicNodeQortalRequest, AdminActionQortalRequest, OpenNewTabQortalRequest, ShowActionsQortalRequest, SignTransactionQortalRequest, CreateAndCopyEmbedLinkQortalRequest, TransferAssetQortalRequest, ShowPdfReaderQortalRequest, SaveFileQortalRequest, GetPrimaryNameQortalRequest, ScreenOrientation, GetNodeStatusQortalRequest, GetNodeInfoQortalRequest, } from "./types/qortalRequests/interfaces"
|
||||
|
||||
|
||||
declare global {
|
||||
@ -84,7 +84,7 @@ declare global {
|
||||
CreateAndCopyEmbedLinkQortalRequest |
|
||||
TransferAssetQortalRequest |
|
||||
ShowPdfReaderQortalRequest |
|
||||
SaveFileQortalRequest | GetPrimaryNameQortalRequest
|
||||
SaveFileQortalRequest | GetPrimaryNameQortalRequest | ScreenOrientation | GetNodeStatusQortalRequest | GetNodeInfoQortalRequest;
|
||||
|
||||
|
||||
function qortalRequest(options: QortalRequestOptions): Promise<any>
|
||||
|
@ -1,46 +1,65 @@
|
||||
// GlobalVideoPlayer.tsx
|
||||
import videojs from 'video.js';
|
||||
import { useGlobalPlayerStore } from '../state/pip';
|
||||
import { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
||||
import { Box, IconButton } from '@mui/material';
|
||||
import { VideoContainer } from '../components/VideoPlayer/VideoPlayer-styles';
|
||||
import videojs from "video.js";
|
||||
import { useGlobalPlayerStore } from "../state/pip";
|
||||
import { useCallback, useContext, useEffect, useRef, useState } from "react";
|
||||
import { Box, IconButton } from "@mui/material";
|
||||
import { VideoContainer } from "../components/VideoPlayer/VideoPlayer-styles";
|
||||
import { Rnd } from "react-rnd";
|
||||
import { useProgressStore } from '../state/video';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
||||
import PauseIcon from '@mui/icons-material/Pause';
|
||||
import OpenInFullIcon from '@mui/icons-material/OpenInFull';
|
||||
import { GlobalContext } from '../context/GlobalProvider';
|
||||
import { useProgressStore } from "../state/video";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
|
||||
import PauseIcon from "@mui/icons-material/Pause";
|
||||
import OpenInFullIcon from "@mui/icons-material/OpenInFull";
|
||||
import { GlobalContext } from "../context/GlobalProvider";
|
||||
import { isTouchDevice } from "../components/VideoPlayer/VideoPlayer";
|
||||
export const GlobalPipPlayer = () => {
|
||||
const { videoSrc, reset, isPlaying, location, type, currentTime, mode, videoId } = useGlobalPlayerStore();
|
||||
const [playing , setPlaying] = useState(false)
|
||||
const [hasStarted, setHasStarted] = useState(false)
|
||||
const {
|
||||
videoSrc,
|
||||
reset,
|
||||
isPlaying,
|
||||
location,
|
||||
type,
|
||||
currentTime,
|
||||
mode,
|
||||
videoId,
|
||||
} = useGlobalPlayerStore();
|
||||
const [playing, setPlaying] = useState(false);
|
||||
const [hasStarted, setHasStarted] = useState(false);
|
||||
const playerRef = useRef<any>(null);
|
||||
const context = useContext(GlobalContext)
|
||||
const navigate = context?.navigate
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const context = useContext(GlobalContext);
|
||||
const navigate = context?.navigate;
|
||||
const videoNode = useRef<HTMLVideoElement>(null);
|
||||
const { setProgress } = useProgressStore();
|
||||
const hideTimeoutRef = useRef<number | null>(null);
|
||||
const { setProgress } = useProgressStore();
|
||||
|
||||
const updateProgress = useCallback(() => {
|
||||
const player = playerRef?.current;
|
||||
if (!player || typeof player?.currentTime !== "function") return;
|
||||
const updateProgress = useCallback(() => {
|
||||
const player = playerRef?.current;
|
||||
if (!player || typeof player?.currentTime !== "function") return;
|
||||
|
||||
const currentTime = player.currentTime();
|
||||
if (typeof currentTime === "number" && videoId && currentTime > 0.1) {
|
||||
setProgress(videoId, currentTime);
|
||||
}
|
||||
}, [videoId]);
|
||||
const currentTime = player.currentTime();
|
||||
if (typeof currentTime === "number" && videoId && currentTime > 0.1) {
|
||||
setProgress(videoId, currentTime);
|
||||
}
|
||||
}, [videoId]);
|
||||
|
||||
const rndRef = useRef<any>(null)
|
||||
const rndRef = useRef<any>(null);
|
||||
useEffect(() => {
|
||||
if (!playerRef.current && videoNode.current) {
|
||||
playerRef.current = videojs(videoNode.current, { autoplay: true, controls: false,
|
||||
responsive: true, fluid: true });
|
||||
playerRef.current = videojs(videoNode.current, {
|
||||
autoplay: true,
|
||||
controls: false,
|
||||
responsive: true,
|
||||
fluid: true,
|
||||
});
|
||||
playerRef.current?.on("error", () => {
|
||||
// Optional: display user-friendly message
|
||||
});
|
||||
|
||||
// Resume playback if needed
|
||||
playerRef.current.on('ready', () => {
|
||||
playerRef.current.on("ready", () => {
|
||||
if (videoSrc) {
|
||||
|
||||
playerRef.current.src(videoSrc);
|
||||
playerRef.current.currentTime(currentTime);
|
||||
if (isPlaying) playerRef.current.play();
|
||||
@ -53,181 +72,216 @@ export const GlobalPipPlayer = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(()=> {
|
||||
if(!videoSrc){
|
||||
setHasStarted(false)
|
||||
}
|
||||
}, [videoSrc])
|
||||
|
||||
useEffect(() => {
|
||||
const player = playerRef.current;
|
||||
|
||||
if (!player) return;
|
||||
|
||||
if (!videoSrc && player.src) {
|
||||
// Only pause the player and unload the source without re-triggering playback
|
||||
player.pause();
|
||||
|
||||
// Remove the video source safely
|
||||
const tech = player.tech({ IWillNotUseThisInPlugins: true });
|
||||
if (tech && tech.el_) {
|
||||
tech.setAttribute('src', '');
|
||||
setPlaying(false)
|
||||
setHasStarted(false)
|
||||
}
|
||||
|
||||
// Optionally clear the poster and currentTime
|
||||
player.poster('');
|
||||
player.currentTime(0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if(videoSrc){
|
||||
// Set source and resume if needed
|
||||
player.src({ src: videoSrc, type: type });
|
||||
player.currentTime(currentTime);
|
||||
|
||||
if (isPlaying) {
|
||||
const playPromise = player.play();
|
||||
|
||||
|
||||
if (playPromise?.catch) {
|
||||
playPromise.catch((err: any) => {
|
||||
console.warn('Unable to autoplay:', err);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
player.pause();
|
||||
}
|
||||
}
|
||||
}, [videoSrc, type, isPlaying, currentTime]);
|
||||
|
||||
|
||||
// const onDragStart = () => {
|
||||
// timer = Date.now();
|
||||
// isDragging.current = true;
|
||||
// };
|
||||
|
||||
// const handleStopDrag = async () => {
|
||||
// const time = Date.now();
|
||||
// if (timer && time - timer < 300) {
|
||||
// isDragging.current = false;
|
||||
// } else {
|
||||
// isDragging.current = true;
|
||||
// }
|
||||
// };
|
||||
// const onDragStop = () => {
|
||||
// handleStopDrag();
|
||||
// };
|
||||
|
||||
// const checkIfDrag = useCallback(() => {
|
||||
// return isDragging.current;
|
||||
// }, []);
|
||||
const margin = 50;
|
||||
|
||||
|
||||
const [height, setHeight] = useState(300)
|
||||
const [width, setWidth] = useState(400)
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
rndRef.current.updatePosition({
|
||||
x: window.innerWidth - (width || 400) - margin,
|
||||
y: window.innerHeight - (height || 300) - margin,
|
||||
width: width || 400,
|
||||
height: height || 300
|
||||
});
|
||||
if (!videoSrc) {
|
||||
setHasStarted(false);
|
||||
}
|
||||
}, [videoSrc]);
|
||||
|
||||
const [showControls, setShowControls] = useState(false)
|
||||
useEffect(() => {
|
||||
const player = playerRef.current;
|
||||
|
||||
const handleMouseMove = () => {
|
||||
setShowControls(true)
|
||||
if (!player) return;
|
||||
|
||||
if (!videoSrc && player.src) {
|
||||
// Only pause the player and unload the source without re-triggering playback
|
||||
player.pause();
|
||||
|
||||
|
||||
// player.src({ src: '', type: '' }); // ⬅️ this is the safe way to clear it
|
||||
|
||||
|
||||
setPlaying(false);
|
||||
setHasStarted(false);
|
||||
|
||||
// Optionally clear the poster and currentTime
|
||||
player.poster("");
|
||||
player.currentTime(0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (videoSrc) {
|
||||
// Set source and resume if needed
|
||||
player.src({ src: videoSrc, type: type });
|
||||
player.currentTime(currentTime);
|
||||
|
||||
if (isPlaying) {
|
||||
const playPromise = player.play();
|
||||
|
||||
if (playPromise?.catch) {
|
||||
playPromise.catch((err: any) => {
|
||||
console.warn("Unable to autoplay:", err);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
player.pause();
|
||||
}
|
||||
}
|
||||
}, [videoSrc, type, isPlaying, currentTime]);
|
||||
|
||||
// const onDragStart = () => {
|
||||
// timer = Date.now();
|
||||
// isDragging.current = true;
|
||||
// };
|
||||
|
||||
// const handleStopDrag = async () => {
|
||||
// const time = Date.now();
|
||||
// if (timer && time - timer < 300) {
|
||||
// isDragging.current = false;
|
||||
// } else {
|
||||
// isDragging.current = true;
|
||||
// }
|
||||
// };
|
||||
// const onDragStop = () => {
|
||||
// handleStopDrag();
|
||||
// };
|
||||
|
||||
// const checkIfDrag = useCallback(() => {
|
||||
// return isDragging.current;
|
||||
// }, []);
|
||||
const margin = 50;
|
||||
|
||||
const [height, setHeight] = useState(300);
|
||||
const [width, setWidth] = useState(400);
|
||||
|
||||
useEffect(() => {
|
||||
if (!videoSrc) return;
|
||||
|
||||
const screenWidth = window.innerWidth;
|
||||
const screenHeight = window.innerHeight;
|
||||
const aspectRatio = 0.75; // 300 / 400 = 3:4
|
||||
|
||||
const maxWidthByScreen = screenWidth * 0.75;
|
||||
const maxWidthByHeight = (screenHeight * 0.2) / aspectRatio;
|
||||
|
||||
const maxWidth = Math.min(maxWidthByScreen, maxWidthByHeight);
|
||||
const maxHeight = maxWidth * aspectRatio;
|
||||
|
||||
setWidth(maxWidth);
|
||||
setHeight(maxHeight);
|
||||
|
||||
rndRef.current.updatePosition({
|
||||
x: screenWidth - maxWidth - margin,
|
||||
y: screenHeight - maxHeight - margin,
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
});
|
||||
}, [videoSrc]);
|
||||
|
||||
const [showControls, setShowControls] = useState(false);
|
||||
|
||||
const handleMouseMove = () => {
|
||||
if (isTouchDevice) return;
|
||||
setShowControls(true);
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
if (isTouchDevice) return;
|
||||
setShowControls(false);
|
||||
};
|
||||
const startPlay = useCallback(() => {
|
||||
try {
|
||||
|
||||
const player = playerRef.current;
|
||||
if (!player) return;
|
||||
|
||||
try {
|
||||
const player = playerRef.current;
|
||||
if (!player) return;
|
||||
|
||||
try {
|
||||
player.play();
|
||||
player.play();
|
||||
} catch (err) {
|
||||
console.warn('Play failed:', err);
|
||||
console.warn("Play failed:", err);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('togglePlay', error)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("togglePlay", error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const stopPlay = useCallback(() => {
|
||||
const player = playerRef.current;
|
||||
if (!player) return;
|
||||
|
||||
try {
|
||||
player.pause();
|
||||
} catch (err) {
|
||||
console.warn('Play failed:', err);
|
||||
}
|
||||
|
||||
if (!player) return;
|
||||
|
||||
try {
|
||||
player.pause();
|
||||
} catch (err) {
|
||||
console.warn("Play failed:", err);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onPlayHandlerStart = useCallback(() => {
|
||||
setPlaying(true)
|
||||
setHasStarted(true)
|
||||
setPlaying(true);
|
||||
setHasStarted(true);
|
||||
}, [setPlaying]);
|
||||
const onPlayHandlerStop = useCallback(() => {
|
||||
setPlaying(false)
|
||||
const onPlayHandlerStop = useCallback(() => {
|
||||
setPlaying(false);
|
||||
}, [setPlaying]);
|
||||
|
||||
const resetHideTimeout = () => {
|
||||
setShowControls(true);
|
||||
if (hideTimeoutRef.current) clearTimeout(hideTimeoutRef.current);
|
||||
hideTimeoutRef.current = setTimeout(() => {
|
||||
setShowControls(false);
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
if (!videoSrc || !container) return;
|
||||
|
||||
const handleInteraction = () => {
|
||||
console.log("Touchstart detected!");
|
||||
resetHideTimeout();
|
||||
};
|
||||
|
||||
container.addEventListener("touchstart", handleInteraction, {
|
||||
passive: true,
|
||||
capture: true,
|
||||
});
|
||||
|
||||
return () => {
|
||||
container.removeEventListener("touchstart", handleInteraction, {
|
||||
capture: true,
|
||||
});
|
||||
};
|
||||
}, [videoSrc]);
|
||||
|
||||
console.log("showControls", showControls);
|
||||
|
||||
return (
|
||||
<Rnd
|
||||
enableResizing={{
|
||||
top: false,
|
||||
right: false,
|
||||
bottom: false,
|
||||
left: false,
|
||||
topRight: true,
|
||||
bottomLeft: true,
|
||||
topLeft: true,
|
||||
bottomRight: true,
|
||||
}}
|
||||
enableResizing={{
|
||||
top: false,
|
||||
right: false,
|
||||
bottom: false,
|
||||
left: false,
|
||||
topRight: true,
|
||||
bottomLeft: true,
|
||||
topLeft: true,
|
||||
bottomRight: true,
|
||||
}}
|
||||
ref={rndRef}
|
||||
// onDragStart={onDragStart}
|
||||
// onDragStop={onDragStop}
|
||||
style={{
|
||||
display: hasStarted ? "block" : "none",
|
||||
position: "fixed",
|
||||
zIndex: 999999999,
|
||||
|
||||
ref={rndRef}
|
||||
// onDragStart={onDragStart}
|
||||
// onDragStop={onDragStop}
|
||||
style={{
|
||||
display: hasStarted ? "block" : "none",
|
||||
position: "fixed",
|
||||
zIndex: 999999999,
|
||||
|
||||
cursor: 'default'
|
||||
}}
|
||||
size={{ width, height }}
|
||||
onResize={(e, direction, ref, delta, position) => {
|
||||
setWidth(ref.offsetWidth);
|
||||
setHeight(ref.offsetHeight);
|
||||
}}
|
||||
|
||||
// default={{
|
||||
// x: 500,
|
||||
// y: 500,
|
||||
// width: 350,
|
||||
// height: "auto",
|
||||
// }}
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
onDrag={() => {}}
|
||||
>
|
||||
{/* <div
|
||||
cursor: "default",
|
||||
}}
|
||||
size={{ width, height }}
|
||||
onResize={(e, direction, ref, delta, position) => {
|
||||
setWidth(ref.offsetWidth);
|
||||
setHeight(ref.offsetHeight);
|
||||
}}
|
||||
// default={{
|
||||
// x: 500,
|
||||
// y: 500,
|
||||
// width: 350,
|
||||
// height: "auto",
|
||||
// }}
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
onDrag={() => {}}
|
||||
>
|
||||
{/* <div
|
||||
style={{
|
||||
position: 'fixed',
|
||||
bottom: '20px',
|
||||
@ -238,90 +292,135 @@ const margin = 50;
|
||||
display: videoSrc ? 'block' : 'none'
|
||||
}}
|
||||
> */}
|
||||
<Box sx={{height, width, position: 'relative' , background: 'black', overflow: 'hidden', borderRadius: '10px' }} onMouseMove={handleMouseMove}
|
||||
onMouseLeave={handleMouseLeave}>
|
||||
{/* {backgroundColor: showControls ? 'rgba(0,0,0,.5)' : 'unset'} */}
|
||||
{showControls && (
|
||||
<Box sx={{
|
||||
position: 'absolute',
|
||||
top: 0, bottom: 0, left: 0, right: 0,
|
||||
zIndex: 1,
|
||||
opacity: 0,
|
||||
transition: 'opacity 1s',
|
||||
"&:hover": {
|
||||
opacity: 1
|
||||
}
|
||||
}}>
|
||||
<Box sx={{
|
||||
position: 'absolute',
|
||||
background: 'rgba(0,0,0,.5)',
|
||||
top: 0, bottom: 0, left: 0, right: 0,
|
||||
zIndex: 1,
|
||||
opacity: 0,
|
||||
transition: 'opacity 1s',
|
||||
"&:hover": {
|
||||
opacity: 1
|
||||
}
|
||||
}} />
|
||||
<IconButton sx={{
|
||||
position: 'absolute',
|
||||
top: 10,
|
||||
opacity: 1,
|
||||
right: 10,
|
||||
zIndex: 2,
|
||||
|
||||
}} onClick={reset}><CloseIcon /></IconButton>
|
||||
{location && (
|
||||
<IconButton sx={{
|
||||
position: 'absolute',
|
||||
top: 10,
|
||||
left: 10,
|
||||
zIndex: 2,
|
||||
opacity: 1,
|
||||
|
||||
}} onClick={()=> {
|
||||
if(navigate){
|
||||
navigate(location)
|
||||
}
|
||||
}}><OpenInFullIcon /></IconButton>
|
||||
)}
|
||||
{playing && (
|
||||
<IconButton sx={{
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
opacity: 1,
|
||||
zIndex: 2,
|
||||
|
||||
}} onClick={stopPlay}><PauseIcon /></IconButton>
|
||||
)}
|
||||
{!playing && (
|
||||
<IconButton sx={{
|
||||
position: 'absolute',
|
||||
opacity: 1,
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
zIndex: 2,
|
||||
|
||||
}} onClick={startPlay}><PlayArrowIcon /></IconButton>
|
||||
)}
|
||||
|
||||
|
||||
|
||||
<Box/>
|
||||
</Box>
|
||||
<Box
|
||||
ref={containerRef}
|
||||
sx={{
|
||||
height,
|
||||
pointerEvents: "auto",
|
||||
width,
|
||||
position: "relative",
|
||||
background: "black",
|
||||
overflow: "hidden",
|
||||
borderRadius: "10px",
|
||||
}}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
{/* {backgroundColor: showControls ? 'rgba(0,0,0,.5)' : 'unset'} */}
|
||||
{showControls && (
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
zIndex: 1,
|
||||
opacity: showControls ? 1 : 0,
|
||||
pointerEvents: showControls ? "auto" : "none",
|
||||
transition: "opacity 0.5s ease-in-out",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
background: "rgba(0,0,0,.5)",
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
zIndex: 1,
|
||||
opacity: showControls ? 1 : 0,
|
||||
pointerEvents: showControls ? "auto" : "none",
|
||||
transition: "opacity 0.5s ease-in-out",
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: 10,
|
||||
opacity: 1,
|
||||
right: 10,
|
||||
zIndex: 2,
|
||||
}}
|
||||
onClick={reset}
|
||||
onTouchStart={reset}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
{location && (
|
||||
<IconButton
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: 10,
|
||||
left: 10,
|
||||
zIndex: 2,
|
||||
opacity: 1,
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
if (navigate) {
|
||||
navigate(location);
|
||||
}
|
||||
}}
|
||||
onTouchStart={(e) => {
|
||||
e.stopPropagation()
|
||||
if (navigate) {
|
||||
navigate(location);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<OpenInFullIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
{playing && (
|
||||
<IconButton
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
opacity: 1,
|
||||
zIndex: 2,
|
||||
}}
|
||||
onClick={stopPlay}
|
||||
onTouchStart={stopPlay}
|
||||
>
|
||||
<PauseIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
{!playing && (
|
||||
<IconButton
|
||||
sx={{
|
||||
position: "absolute",
|
||||
opacity: 1,
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
zIndex: 2,
|
||||
}}
|
||||
onClick={startPlay}
|
||||
onTouchStart={startPlay}
|
||||
>
|
||||
<PlayArrowIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
|
||||
<Box />
|
||||
</Box>
|
||||
)}
|
||||
<VideoContainer>
|
||||
<video onPlay={onPlayHandlerStart} onPause={onPlayHandlerStop} onTimeUpdate={updateProgress}
|
||||
ref={videoNode} className="video-js" style={{
|
||||
|
||||
|
||||
}}/>
|
||||
</VideoContainer>
|
||||
<video
|
||||
onPlay={onPlayHandlerStart}
|
||||
onPause={onPlayHandlerStop}
|
||||
onTimeUpdate={updateProgress}
|
||||
ref={videoNode}
|
||||
className="video-js"
|
||||
style={{}}
|
||||
/>
|
||||
</VideoContainer>
|
||||
</Box>
|
||||
{/* </div> */}
|
||||
{/* </div> */}
|
||||
</Rnd>
|
||||
);
|
||||
};
|
||||
|
@ -615,12 +615,29 @@ export interface ShowActionsQortalRequest extends BaseRequest {
|
||||
action: 'SHOW_ACTIONS'
|
||||
}
|
||||
|
||||
export interface ScreenOrientation extends BaseRequest {
|
||||
action: 'SCREEN_ORIENTATION',
|
||||
mode: | "portrait"
|
||||
| "landscape"
|
||||
| "portrait-primary"
|
||||
| "portrait-secondary"
|
||||
| "landscape-primary"
|
||||
| "landscape-secondary" | "unlock";
|
||||
}
|
||||
|
||||
export interface SignTransactionQortalRequest extends BaseRequest {
|
||||
action: 'SIGN_TRANSACTION'
|
||||
unsignedBytes: string
|
||||
process?: boolean
|
||||
}
|
||||
|
||||
export interface GetNodeStatusQortalRequest extends BaseRequest {
|
||||
action: 'GET_NODE_STATUS'
|
||||
}
|
||||
export interface GetNodeInfoQortalRequest extends BaseRequest {
|
||||
action: 'GET_NODE_INFO'
|
||||
}
|
||||
|
||||
export interface CreateAndCopyEmbedLinkQortalRequest extends BaseRequest {
|
||||
action: 'CREATE_AND_COPY_EMBED_LINK'
|
||||
type: string
|
||||
|
Loading…
x
Reference in New Issue
Block a user