From 9eaecac51caefa661977be296bf17832a4ca546f Mon Sep 17 00:00:00 2001 From: HYUN-SIUU Date: Fri, 27 Feb 2026 11:47:33 +0900 Subject: [PATCH] =?UTF-8?q?full=20screen=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exercise/members/panel/MemberInfo.tsx | 2 +- src/components/VideoPlayer.tsx | 69 +++++++++++++++++-- src/components/video-player/VideoControls.tsx | 14 ++++ 3 files changed, 80 insertions(+), 5 deletions(-) diff --git a/src/app/(with-container)/(exercise)/exercise/members/panel/MemberInfo.tsx b/src/app/(with-container)/(exercise)/exercise/members/panel/MemberInfo.tsx index ff4deb4..26757bd 100644 --- a/src/app/(with-container)/(exercise)/exercise/members/panel/MemberInfo.tsx +++ b/src/app/(with-container)/(exercise)/exercise/members/panel/MemberInfo.tsx @@ -40,7 +40,7 @@ const MemberInfo = ({ name, birthDate, gender, memberRank }: IMember) => { {gradeLabel[memberRank]} diff --git a/src/components/VideoPlayer.tsx b/src/components/VideoPlayer.tsx index 64f04e5..64db431 100644 --- a/src/components/VideoPlayer.tsx +++ b/src/components/VideoPlayer.tsx @@ -19,6 +19,7 @@ export interface IVideoHandle { toggle: () => void; mute: (m?: boolean) => void; seek: (timeSec: number) => void; + toggleFullscreen: () => void; getEl: () => HTMLVideoElement | null; } @@ -78,6 +79,8 @@ const VideoPlayer = forwardRef( ref, ) { const videoRef = useRef(null); + const wrapperRef = useRef(null); + const [isFullscreen, setIsFullscreen] = useState(false); // controlsRef removed; controls are separate component // 재생/일시정지 플래시 아이콘 @@ -100,6 +103,43 @@ const VideoPlayer = forwardRef( }, 650); }; + const handleToggleFullscreen = useCallback(() => { + if (!wrapperRef.current) return; + + if (!document.fullscreenElement) { + wrapperRef.current.requestFullscreen().catch((err) => { + console.error( + `Error attempting to enable full-screen mode: ${err.message}`, + ); + }); + } else { + document.exitFullscreen(); + } + }, []); + + useEffect(() => { + const handleFullscreenChange = () => { + setIsFullscreen(!!document.fullscreenElement); + }; + + document.addEventListener("fullscreenchange", handleFullscreenChange); + document.addEventListener( + "webkitfullscreenchange", + handleFullscreenChange, + ); + + return () => { + document.removeEventListener( + "fullscreenchange", + handleFullscreenChange, + ); + document.removeEventListener( + "webkitfullscreenchange", + handleFullscreenChange, + ); + }; + }, []); + const { playing, muted, @@ -127,6 +167,7 @@ const VideoPlayer = forwardRef( toggle: () => toggle?.(), mute: (m?: boolean) => mute?.(m), seek: (t: number) => seek?.(t), + toggleFullscreen: handleToggleFullscreen, getEl: () => videoRef.current, })); @@ -193,24 +234,42 @@ const VideoPlayer = forwardRef( const ratioNum = parseAspectRatioToNumber(aspectRatio); const aspectCss = typeof aspectRatio === "number" ? aspectRatio : (aspectRatio ?? "16 / 9"); - const maxWFromHeightExpr = `calc((100dvh - ${viewportOffsetPx}px) * ${ratioNum})`; + const maxWFromHeightExpr = `calc((100dvh - ${isFullscreen ? 0 : viewportOffsetPx}px) * ${ratioNum})`; return ( - + {/* 비디오 영역: 남은 높이를 꽉 채우되 16:9 유지 */} showControls()} onPointerMove={() => showControls()} @@ -316,6 +375,8 @@ const VideoPlayer = forwardRef( v.currentTime = next; setScrub(null); }} + isFullscreen={isFullscreen} + onToggleFullscreen={handleToggleFullscreen} visible={controlsVisible} /> diff --git a/src/components/video-player/VideoControls.tsx b/src/components/video-player/VideoControls.tsx index 94babd5..eecc644 100644 --- a/src/components/video-player/VideoControls.tsx +++ b/src/components/video-player/VideoControls.tsx @@ -4,6 +4,8 @@ import PlayArrowRounded from "@mui/icons-material/PlayArrowRounded"; import PauseRounded from "@mui/icons-material/PauseRounded"; import VolumeUpRounded from "@mui/icons-material/VolumeUpRounded"; import VolumeOffRounded from "@mui/icons-material/VolumeOffRounded"; +import FullscreenRounded from "@mui/icons-material/FullscreenRounded"; +import FullscreenExitRounded from "@mui/icons-material/FullscreenExitRounded"; const formatTime = (sec: number) => Number.isFinite(sec) @@ -25,6 +27,8 @@ interface Props { onToggleMute: () => void; setScrub: (v: number | null) => void; onSeek: (sec: number) => void; + isFullscreen: boolean; + onToggleFullscreen: () => void; visible?: boolean; } @@ -43,6 +47,8 @@ export default function VideoControls({ onToggleMute, setScrub, onSeek, + isFullscreen, + onToggleFullscreen, visible = true, }: Props) { return ( @@ -110,6 +116,14 @@ export default function VideoControls({ }, }} /> + + + {isFullscreen ? : } + ); }