[251112] feat: ProductVideroV2 Continuous Video Playing
🕐 커밋 시간: 2025. 11. 12. 15:28:31 📊 변경 통계: • 총 파일: 2개 • 추가: +67줄 • 삭제: -31줄 📝 수정된 파일: ~ com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.module.less ~ com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v2.jsx 🔧 주요 변경 내용: • 소규모 기능 개선
This commit is contained in:
@@ -143,6 +143,16 @@
|
|||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hideWhileFullscreen {
|
||||||
|
visibility: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoPortalHost {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
// 전체화면 container (Portal, body에 항상 존재)
|
// 전체화면 container (Portal, body에 항상 존재)
|
||||||
.fullscreenContainer {
|
.fullscreenContainer {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react';
|
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useState, useRef } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import Spottable from '@enact/spotlight/Spottable';
|
import Spottable from '@enact/spotlight/Spottable';
|
||||||
@@ -24,7 +24,11 @@ import {
|
|||||||
stopMediaAutoClose,
|
stopMediaAutoClose,
|
||||||
resetMediaAutoClose,
|
resetMediaAutoClose,
|
||||||
} from '../../../../actions/mediaOverlayActions';
|
} from '../../../../actions/mediaOverlayActions';
|
||||||
import { pauseFullscreenVideo, resumeFullscreenVideo, clearAllVideoTimers } from '../../../../actions/playActions';
|
import {
|
||||||
|
pauseFullscreenVideo,
|
||||||
|
resumeFullscreenVideo,
|
||||||
|
clearAllVideoTimers,
|
||||||
|
} from '../../../../actions/playActions';
|
||||||
import css from './ProductVideo.module.less';
|
import css from './ProductVideo.module.less';
|
||||||
|
|
||||||
const SpottableComponent = Spottable('div');
|
const SpottableComponent = Spottable('div');
|
||||||
@@ -80,6 +84,16 @@ export function ProductVideoV2({
|
|||||||
const videoPlayerWrapperRef = useRef(null);
|
const videoPlayerWrapperRef = useRef(null);
|
||||||
const normalContainerRef = useRef(null);
|
const normalContainerRef = useRef(null);
|
||||||
const fullscreenContainerRef = useRef(null);
|
const fullscreenContainerRef = useRef(null);
|
||||||
|
const videoPortalHostRef = useRef(null);
|
||||||
|
|
||||||
|
const ensurePortalHost = useCallback(() => {
|
||||||
|
if (!videoPortalHostRef.current && typeof document !== 'undefined') {
|
||||||
|
const host = document.createElement('div');
|
||||||
|
host.className = css.videoPortalHost;
|
||||||
|
videoPortalHostRef.current = host;
|
||||||
|
}
|
||||||
|
return videoPortalHostRef.current;
|
||||||
|
}, []);
|
||||||
|
|
||||||
// 비디오 재생 가능 여부 체크
|
// 비디오 재생 가능 여부 체크
|
||||||
const canPlayVideo = useMemo(() => {
|
const canPlayVideo = useMemo(() => {
|
||||||
@@ -149,33 +163,36 @@ export function ProductVideoV2({
|
|||||||
}, [canPlayVideo, isPlaying]);
|
}, [canPlayVideo, isPlaying]);
|
||||||
|
|
||||||
// 썸네일 클릭 핸들러 - 비디오 재생 시작 + Redux dispatch + MediaPlayer 메서드 호출
|
// 썸네일 클릭 핸들러 - 비디오 재생 시작 + Redux dispatch + MediaPlayer 메서드 호출
|
||||||
const handleThumbnailClick = useCallback((e) => {
|
const handleThumbnailClick = useCallback(
|
||||||
console.log('🎬 [handleThumbnailClick] 썸네일 클릭됨', {
|
(e) => {
|
||||||
canPlayVideo,
|
console.log('🎬 [handleThumbnailClick] 썸네일 클릭됨', {
|
||||||
isPlaying,
|
canPlayVideo,
|
||||||
eventType: e?.type,
|
isPlaying,
|
||||||
target: e?.target?.className,
|
eventType: e?.type,
|
||||||
bubbles: e?.bubbles,
|
target: e?.target?.className,
|
||||||
timestamp: new Date().getTime(),
|
bubbles: e?.bubbles,
|
||||||
});
|
timestamp: new Date().getTime(),
|
||||||
if (canPlayVideo && !isPlaying) {
|
});
|
||||||
// console.log('[BgVideo] ProductVideoV2 - Starting video playback');
|
if (canPlayVideo && !isPlaying) {
|
||||||
console.log('🎬 [handleThumbnailClick] ✅ 비디오 재생 시작');
|
// console.log('[BgVideo] ProductVideoV2 - Starting video playback');
|
||||||
setIsPlaying(true);
|
console.log('🎬 [handleThumbnailClick] ✅ 비디오 재생 시작');
|
||||||
|
setIsPlaying(true);
|
||||||
|
|
||||||
// 백그라운드 전체화면 비디오 일시정지
|
// 백그라운드 전체화면 비디오 일시정지
|
||||||
// console.log('[BgVideo] ProductVideoV2 - Pausing background fullscreen video');
|
// console.log('[BgVideo] ProductVideoV2 - Pausing background fullscreen video');
|
||||||
dispatch(pauseFullscreenVideo());
|
dispatch(pauseFullscreenVideo());
|
||||||
|
|
||||||
// Redux: mediaOverlay 상태 초기화 (MediaPlayer 전용)
|
// Redux: mediaOverlay 상태 초기화 (MediaPlayer 전용)
|
||||||
dispatch(setMediaControlShow());
|
dispatch(setMediaControlShow());
|
||||||
dispatch(startMediaAutoClose(3000));
|
dispatch(startMediaAutoClose(3000));
|
||||||
// MediaPlayer 직접 제어: controls 표시
|
// MediaPlayer 직접 제어: controls 표시
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
videoPlayerRef.current?.showControls?.();
|
videoPlayerRef.current?.showControls?.();
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
}, [canPlayVideo, isPlaying, dispatch]);
|
},
|
||||||
|
[canPlayVideo, isPlaying, dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
// 비디오 종료 핸들러 - 썸네일로 복귀 + Redux cleanup + MediaPlayer 메서드 호출
|
// 비디오 종료 핸들러 - 썸네일로 복귀 + Redux cleanup + MediaPlayer 메서드 호출
|
||||||
const handleVideoEnded = useCallback(() => {
|
const handleVideoEnded = useCallback(() => {
|
||||||
@@ -372,10 +389,10 @@ export function ProductVideoV2({
|
|||||||
const isCurrentlyVisible = videoPlayerRef.current?.areControlsVisible?.();
|
const isCurrentlyVisible = videoPlayerRef.current?.areControlsVisible?.();
|
||||||
if (isCurrentlyVisible) {
|
if (isCurrentlyVisible) {
|
||||||
videoPlayerRef.current?.hideControls?.();
|
videoPlayerRef.current?.hideControls?.();
|
||||||
dispatch(setMediaControlHide()); // MediaOverlay Redux 상태 동기화
|
dispatch(setMediaControlHide()); // MediaOverlay Redux 상태 동기화
|
||||||
} else {
|
} else {
|
||||||
videoPlayerRef.current?.showControls?.();
|
videoPlayerRef.current?.showControls?.();
|
||||||
dispatch(setMediaControlShow()); // MediaOverlay Redux 상태 동기화
|
dispatch(setMediaControlShow()); // MediaOverlay Redux 상태 동기화
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[isPlaying, isFullscreen, toggleFullscreen, dispatch, mediaOverlayState.controls?.visible]
|
[isPlaying, isFullscreen, toggleFullscreen, dispatch, mediaOverlayState.controls?.visible]
|
||||||
@@ -394,34 +411,40 @@ export function ProductVideoV2({
|
|||||||
}, [mediaOverlayState.controls?.visible, dispatch]);
|
}, [mediaOverlayState.controls?.visible, dispatch]);
|
||||||
|
|
||||||
// 전체 화면 컨테이너용 마우스 다운 핸들러 (Capture Phase)
|
// 전체 화면 컨테이너용 마우스 다운 핸들러 (Capture Phase)
|
||||||
const handleFullscreenContainerMouseDownCapture = useCallback((e) => {
|
const handleFullscreenContainerMouseDownCapture = useCallback(
|
||||||
if (!isPlaying || !isFullscreen) return;
|
(e) => {
|
||||||
|
if (!isPlaying || !isFullscreen) return;
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
// toggleOverlayVisibility();
|
|
||||||
}, [isPlaying, isFullscreen, toggleOverlayVisibility]);
|
|
||||||
|
|
||||||
// 전체 화면 키보드 핸들러
|
|
||||||
const handleFullscreenKeyDown = useCallback((e) => {
|
|
||||||
if (!isPlaying || !isFullscreen) return;
|
|
||||||
|
|
||||||
// ESC 키만 오버레이 토글로 처리
|
|
||||||
if (e.key === 'Escape' || e.keyCode === 27) {
|
|
||||||
console.log('🖥️ [Fullscreen Container] ESC 키 - 오버레이 토글 실행');
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
toggleOverlayVisibility();
|
// toggleOverlayVisibility();
|
||||||
return;
|
},
|
||||||
}
|
[isPlaying, isFullscreen, toggleOverlayVisibility]
|
||||||
|
);
|
||||||
|
|
||||||
// Enter 키는 기본 동작 허용 (포커스된 요소의 동작 수행)
|
// 전체 화면 키보드 핸들러
|
||||||
if (e.key === 'Enter' || e.keyCode === 13) {
|
const handleFullscreenKeyDown = useCallback(
|
||||||
console.log('🖥️ [Fullscreen Container] Enter 키 - 포커스된 요소 동작 허용');
|
(e) => {
|
||||||
// Enter 키는 preventDefault하지 않고 기본 동작 허용
|
if (!isPlaying || !isFullscreen) return;
|
||||||
return;
|
|
||||||
}
|
// ESC 키만 오버레이 토글로 처리
|
||||||
}, [isPlaying, isFullscreen, toggleOverlayVisibility]);
|
if (e.key === 'Escape' || e.keyCode === 27) {
|
||||||
|
console.log('🖥️ [Fullscreen Container] ESC 키 - 오버레이 토글 실행');
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
toggleOverlayVisibility();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enter 키는 기본 동작 허용 (포커스된 요소의 동작 수행)
|
||||||
|
if (e.key === 'Enter' || e.keyCode === 13) {
|
||||||
|
console.log('🖥️ [Fullscreen Container] Enter 키 - 포커스된 요소 동작 허용');
|
||||||
|
// Enter 키는 preventDefault하지 않고 기본 동작 허용
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[isPlaying, isFullscreen, toggleOverlayVisibility]
|
||||||
|
);
|
||||||
|
|
||||||
// 마우스 다운 (클릭) 이벤트 - capture phase에서 처리
|
// 마우스 다운 (클릭) 이벤트 - capture phase에서 처리
|
||||||
const handleVideoPlayerMouseDown = useCallback(
|
const handleVideoPlayerMouseDown = useCallback(
|
||||||
@@ -466,20 +489,24 @@ export function ProductVideoV2({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleDocumentClick = (e) => {
|
const handleDocumentClick = (e) => {
|
||||||
// ProductVideoV2 관련 요소인 경우만 로깅
|
// ProductVideoV2 관련 요소인 경우만 로깅
|
||||||
const isVideoElement = e.target?.closest('[class*="videoContainer"]') ||
|
const isVideoElement =
|
||||||
e.target?.closest('[class*="videoPlayer"]') ||
|
e.target?.closest('[class*="videoContainer"]') ||
|
||||||
e.target?.closest('[class*="videoThumbnail"]');
|
e.target?.closest('[class*="videoPlayer"]') ||
|
||||||
|
e.target?.closest('[class*="videoThumbnail"]');
|
||||||
|
|
||||||
if (isVideoElement) {
|
if (isVideoElement) {
|
||||||
console.log('📄 [Document Level] 전역 클릭 감지됨', {
|
console.log('📄 [Document Level] 전역 클릭 감지됨', {
|
||||||
eventPhase: e.eventPhase,
|
eventPhase: e.eventPhase,
|
||||||
bubbles: e.bubbles,
|
bubbles: e.bubbles,
|
||||||
target: e.target?.className,
|
target: e.target?.className,
|
||||||
eventPath: e.composedPath?.().slice(0, 6).map(el => ({
|
eventPath: e
|
||||||
tag: el.tagName,
|
.composedPath?.()
|
||||||
className: el.className,
|
.slice(0, 6)
|
||||||
id: el.id
|
.map((el) => ({
|
||||||
}))
|
tag: el.tagName,
|
||||||
|
className: el.className,
|
||||||
|
id: el.id,
|
||||||
|
})),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -490,6 +517,36 @@ export function ProductVideoV2({
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (!isPlaying) return;
|
||||||
|
|
||||||
|
const host = ensurePortalHost();
|
||||||
|
const target = isFullscreen ? fullscreenContainerRef.current : normalContainerRef.current;
|
||||||
|
|
||||||
|
if (host && target && host.parentNode !== target) {
|
||||||
|
target.appendChild(host);
|
||||||
|
}
|
||||||
|
}, [isPlaying, isFullscreen, ensurePortalHost]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isPlaying) return;
|
||||||
|
|
||||||
|
const host = videoPortalHostRef.current;
|
||||||
|
if (host?.parentNode) {
|
||||||
|
host.parentNode.removeChild(host);
|
||||||
|
}
|
||||||
|
}, [isPlaying]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
const host = videoPortalHostRef.current;
|
||||||
|
if (host?.parentNode) {
|
||||||
|
host.parentNode.removeChild(host);
|
||||||
|
}
|
||||||
|
videoPortalHostRef.current = null;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
// autoPlay 기능: 컴포넌트 마운트 후 500ms 후 자동 재생
|
// autoPlay 기능: 컴포넌트 마운트 후 500ms 후 자동 재생
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (autoPlay && canPlayVideo && !isPlaying) {
|
if (autoPlay && canPlayVideo && !isPlaying) {
|
||||||
@@ -595,17 +652,10 @@ export function ProductVideoV2({
|
|||||||
|
|
||||||
const containerProps = useMemo(() => {
|
const containerProps = useMemo(() => {
|
||||||
const baseProps = {
|
const baseProps = {
|
||||||
onClick: handleVideoContainerClick, // 모든 상태에서 동일하게 적용
|
onClick: handleVideoContainerClick,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isFullscreen) {
|
if (isPlaying) {
|
||||||
return {
|
|
||||||
...baseProps,
|
|
||||||
spotlightRestrict: 'self-only', // 포커스가 밖으로 나가지 않도록
|
|
||||||
spotlightId: 'product-video-v2-fullscreen',
|
|
||||||
onSpotlightDown: handleSpotlightDown, // 전체화면에서도 Down 키 동작
|
|
||||||
};
|
|
||||||
} else if (isPlaying) {
|
|
||||||
return {
|
return {
|
||||||
...baseProps,
|
...baseProps,
|
||||||
spotlightId: 'product-video-v2-playing',
|
spotlightId: 'product-video-v2-playing',
|
||||||
@@ -615,70 +665,89 @@ export function ProductVideoV2({
|
|||||||
} else {
|
} else {
|
||||||
return baseProps;
|
return baseProps;
|
||||||
}
|
}
|
||||||
}, [isFullscreen, isPlaying, handleVideoContainerClick, handleSpotlightDown, handleContainerKeyDown]);
|
}, [
|
||||||
|
isFullscreen,
|
||||||
|
isPlaying,
|
||||||
|
handleVideoContainerClick,
|
||||||
|
handleSpotlightDown,
|
||||||
|
handleContainerKeyDown,
|
||||||
|
]);
|
||||||
|
|
||||||
// ⚠️ 간단한 해결책: 비디오 영역 클릭 시 직접 동작 실행
|
// ⚠️ 간단한 해결책: 비디오 영역 클릭 시 직접 동작 실행
|
||||||
const handleContainerClickFallback = useCallback((e) => {
|
const handleContainerClickFallback = useCallback(
|
||||||
const isThumbnailArea = e.target?.closest('[class*="videoThumbnail"]') ||
|
(e) => {
|
||||||
e.target?.closest('[class*="playButton"]');
|
const isThumbnailArea =
|
||||||
|
e.target?.closest('[class*="videoThumbnail"]') ||
|
||||||
|
e.target?.closest('[class*="playButton"]');
|
||||||
|
|
||||||
const isVideoPlayerArea = e.target?.closest('[class*="videoPlayer"]') ||
|
const isVideoPlayerArea =
|
||||||
e.target?.closest('[class*="VideoPlayer"]');
|
e.target?.closest('[class*="videoPlayer"]') || e.target?.closest('[class*="VideoPlayer"]');
|
||||||
|
|
||||||
// 썸네일 클릭: 비디오 재생 시작
|
// 썸네일 클릭: 비디오 재생 시작
|
||||||
if (isThumbnailArea && !isPlaying) {
|
if (isThumbnailArea && !isPlaying) {
|
||||||
console.log('🎬 [handleContainerClickFallback] 썸네일 클릭 → 비디오 재생 시작');
|
console.log('🎬 [handleContainerClickFallback] 썸네일 클릭 → 비디오 재생 시작');
|
||||||
handleThumbnailClick(e);
|
handleThumbnailClick(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 비디오 재생 중 클릭: 전체화면 토글
|
// 비디오 재생 중 클릭: 전체화면 토글
|
||||||
if (isVideoPlayerArea && isPlaying && !isFullscreen) {
|
if (isVideoPlayerArea && isPlaying && !isFullscreen) {
|
||||||
console.log('🎬 [handleContainerClickFallback] 비디오 클릭 → 전체화면 토글');
|
console.log('🎬 [handleContainerClickFallback] 비디오 클릭 → 전체화면 토글');
|
||||||
toggleFullscreen();
|
toggleFullscreen();
|
||||||
}
|
}
|
||||||
}, [isPlaying, isFullscreen, handleThumbnailClick, toggleFullscreen]);
|
},
|
||||||
|
[isPlaying, isFullscreen, handleThumbnailClick, toggleFullscreen]
|
||||||
|
);
|
||||||
|
|
||||||
// ⚠️ 핵심: TScrollerDetail이 capture phase에서 이벤트를 막으므로,
|
// ⚠️ 핵심: TScrollerDetail이 capture phase에서 이벤트를 막으므로,
|
||||||
// ProductVideoV2 컨테이너 전체에서 capture phase를 다시 열어줌 (Hooks는 early return 전에)
|
// ProductVideoV2 컨테이너 전체에서 capture phase를 다시 열어줌 (Hooks는 early return 전에)
|
||||||
const handleContainerMouseDownCapture = useCallback((e) => {
|
const handleContainerMouseDownCapture = useCallback(
|
||||||
// 비디오 영역인지 확인
|
(e) => {
|
||||||
const isVideoArea = e.currentTarget === containerRef.current;
|
// 비디오 영역인지 확인
|
||||||
|
const isVideoArea = e.currentTarget === containerRef.current;
|
||||||
|
|
||||||
console.log('🔓 [ProductVideoV2 Container] onMouseDownCapture - TScrollerDetail 차단 우회', {
|
console.log('🔓 [ProductVideoV2 Container] onMouseDownCapture - TScrollerDetail 차단 우회', {
|
||||||
isVideoArea,
|
isVideoArea,
|
||||||
target: e.target?.className,
|
target: e.target?.className,
|
||||||
isPlaying,
|
isPlaying,
|
||||||
isFullscreen,
|
isFullscreen,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 전체 화면 모드가 아닐 때만 toggleFullscreen 호출
|
// 전체 화면 모드가 아닐 때만 toggleFullscreen 호출
|
||||||
if (!isFullscreen) {
|
if (!isFullscreen) {
|
||||||
toggleFullscreen();
|
toggleFullscreen();
|
||||||
|
|
||||||
// TScrollerDetail의 onClick 실행을 막기 위해 preventDefault 호출
|
// TScrollerDetail의 onClick 실행을 막기 위해 preventDefault 호출
|
||||||
if (isPlaying) {
|
if (isPlaying) {
|
||||||
console.log('🔓 [ProductVideoV2] TScrollerDetail onClick 실행 차단');
|
console.log('🔓 [ProductVideoV2] TScrollerDetail onClick 실행 차단');
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 전체 화면 모드에서는 오버레이(컨트롤)만 토글
|
||||||
|
console.log('🖥️ [ProductVideoV2.OVERLAY-Toggle] 전체 화면 모드 - 오버레이 토글 시작');
|
||||||
|
console.log(
|
||||||
|
'🖥️ [ProductVideoV2.OVERLAY-Toggle] 현재 오버레이 상태:',
|
||||||
|
overlayState.controls?.visible
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = dispatch(toggleControls());
|
||||||
|
console.log(
|
||||||
|
'🖥️ [ProductVideoV2.OVERLAY-Toggle] toggleControls 액션 디스패치 결과:',
|
||||||
|
result
|
||||||
|
);
|
||||||
|
|
||||||
|
// 디스패치 후 상태 변화 확인 (setTimeout으로 비동기 처리)
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('🖥️ [ProductVideoV2.OVERLAY-Toggle] 액션 디스패치 후 상태 확인 필요');
|
||||||
|
}, 10);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('🖥️ [ProductVideoV2.OVERLAY-ERROR] toggleControls 디스패치 에러:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
},
|
||||||
// 전체 화면 모드에서는 오버레이(컨트롤)만 토글
|
[isPlaying, isFullscreen, dispatch, toggleControls]
|
||||||
console.log('🖥️ [ProductVideoV2.OVERLAY-Toggle] 전체 화면 모드 - 오버레이 토글 시작');
|
);
|
||||||
console.log('🖥️ [ProductVideoV2.OVERLAY-Toggle] 현재 오버레이 상태:', overlayState.controls?.visible);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = dispatch(toggleControls());
|
|
||||||
console.log('🖥️ [ProductVideoV2.OVERLAY-Toggle] toggleControls 액션 디스패치 결과:', result);
|
|
||||||
|
|
||||||
// 디스패치 후 상태 변화 확인 (setTimeout으로 비동기 처리)
|
|
||||||
setTimeout(() => {
|
|
||||||
console.log('🖥️ [ProductVideoV2.OVERLAY-Toggle] 액션 디스패치 후 상태 확인 필요');
|
|
||||||
}, 10);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('🖥️ [ProductVideoV2.OVERLAY-ERROR] toggleControls 디스패치 에러:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [isPlaying, isFullscreen, dispatch, toggleControls]);
|
|
||||||
|
|
||||||
// Early return: 비디오 재생 불가능한 경우
|
// Early return: 비디오 재생 불가능한 경우
|
||||||
if (!canPlayVideo) return null;
|
if (!canPlayVideo) return null;
|
||||||
@@ -687,17 +756,16 @@ export function ProductVideoV2({
|
|||||||
// 전체화면: SpotlightContainer (self-only)
|
// 전체화면: SpotlightContainer (self-only)
|
||||||
// 일반 재생: SpottableComponent (포커스 가능)
|
// 일반 재생: SpottableComponent (포커스 가능)
|
||||||
// 썸네일: div
|
// 썸네일: div
|
||||||
const ContainerComponent = isFullscreen
|
const ContainerComponent = isPlaying ? SpottableComponent : 'div';
|
||||||
? SpotlightContainer
|
|
||||||
: isPlaying
|
|
||||||
? SpottableComponent
|
|
||||||
: 'div';
|
|
||||||
|
|
||||||
// VideoPlayer 컴포넌트 생성 함수
|
// VideoPlayer 컴포넌트 생성 함수
|
||||||
const renderVideoPlayer = () => {
|
const renderVideoPlayer = () => {
|
||||||
if (!isPlaying) return null;
|
if (!isPlaying) return null;
|
||||||
|
|
||||||
return (
|
const host = ensurePortalHost();
|
||||||
|
if (!host) return null;
|
||||||
|
|
||||||
|
return ReactDOM.createPortal(
|
||||||
<div
|
<div
|
||||||
ref={videoPlayerWrapperRef}
|
ref={videoPlayerWrapperRef}
|
||||||
className={`${css.videoPlayerWrapper} ${isFullscreen ? css.fullscreenPlayer : ''}`}
|
className={`${css.videoPlayerWrapper} ${isFullscreen ? css.fullscreenPlayer : ''}`}
|
||||||
@@ -717,9 +785,10 @@ export function ProductVideoV2({
|
|||||||
onMouseDownCapture={(e) => {
|
onMouseDownCapture={(e) => {
|
||||||
// ⚠️ 핵심: VideoPlayer 내부(실제 비디오 영역)에서만 이벤트를 살리고,
|
// ⚠️ 핵심: VideoPlayer 내부(실제 비디오 영역)에서만 이벤트를 살리고,
|
||||||
// 그 외 wrapper 영역에서만 preventDefault
|
// 그 외 wrapper 영역에서만 preventDefault
|
||||||
const isVideoElement = e.target?.closest('[class*="videoPlayer"]') ||
|
const isVideoElement =
|
||||||
e.target?.closest('video') ||
|
e.target?.closest('[class*="videoPlayer"]') ||
|
||||||
e.target?.closest('[class*="react-player"]');
|
e.target?.closest('video') ||
|
||||||
|
e.target?.closest('[class*="react-player"]');
|
||||||
|
|
||||||
console.log('🎬 [videoPlayerWrapper] onMouseDownCapture 실행됨', {
|
console.log('🎬 [videoPlayerWrapper] onMouseDownCapture 실행됨', {
|
||||||
isFullscreen,
|
isFullscreen,
|
||||||
@@ -777,7 +846,8 @@ export function ProductVideoV2({
|
|||||||
<track kind="subtitles" src={productInfo?.prdtMediaSubtitlUrl} default />
|
<track kind="subtitles" src={productInfo?.prdtMediaSubtitlUrl} default />
|
||||||
)}
|
)}
|
||||||
</VideoPlayer>
|
</VideoPlayer>
|
||||||
</div>
|
</div>,
|
||||||
|
host
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -787,7 +857,7 @@ export function ProductVideoV2({
|
|||||||
<ContainerComponent
|
<ContainerComponent
|
||||||
{...containerProps}
|
{...containerProps}
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
className={`${css.videoContainer} ${isFullscreen ? css.fullscreen : ''}`}
|
className={`${css.videoContainer} ${isFullscreen ? css.hideWhileFullscreen : ''}`}
|
||||||
onMouseDownCapture={handleContainerMouseDownCapture}
|
onMouseDownCapture={handleContainerMouseDownCapture}
|
||||||
onClick={handleContainerClickFallback}
|
onClick={handleContainerClickFallback}
|
||||||
>
|
>
|
||||||
@@ -818,8 +888,7 @@ export function ProductVideoV2({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</SpottableComponent>
|
</SpottableComponent>
|
||||||
) : !isFullscreen ? (
|
) : (
|
||||||
// 일반 재생 모드: VideoPlayer를 직접 렌더링
|
|
||||||
<div
|
<div
|
||||||
ref={normalContainerRef}
|
ref={normalContainerRef}
|
||||||
className={css.normalPlayerContainer}
|
className={css.normalPlayerContainer}
|
||||||
@@ -833,10 +902,9 @@ export function ProductVideoV2({
|
|||||||
handleVideoPlayerClick(e);
|
handleVideoPlayerClick(e);
|
||||||
}}
|
}}
|
||||||
onMouseDownCapture={(e) => {
|
onMouseDownCapture={(e) => {
|
||||||
// ⚠️ 핵심: VideoPlayer 영역 내에서만 이벤트를 살리고,
|
const isVideoPlayerArea =
|
||||||
// 그 외 영역(스크롤 영역)에서만 preventDefault
|
e.target?.closest('[class*="videoPlayer"]') ||
|
||||||
const isVideoPlayerArea = e.target?.closest('[class*="videoPlayer"]') ||
|
e.target?.closest('[class*="VideoPlayer"]');
|
||||||
e.target?.closest('[class*="VideoPlayer"]');
|
|
||||||
|
|
||||||
console.log('🎬 [normalContainerRef] onMouseDownCapture 실행됨', {
|
console.log('🎬 [normalContainerRef] onMouseDownCapture 실행됨', {
|
||||||
isPlaying,
|
isPlaying,
|
||||||
@@ -845,39 +913,34 @@ export function ProductVideoV2({
|
|||||||
target: e.target?.className,
|
target: e.target?.className,
|
||||||
});
|
});
|
||||||
|
|
||||||
// VideoPlayer가 아닌 영역(스크롤 영역)에서만 preventDefault
|
|
||||||
if (!isVideoPlayerArea) {
|
if (!isVideoPlayerArea) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
console.log('🛑 [normalContainerRef] preventDefault - 스크롤 영역에서의 클릭');
|
console.log('🛑 [normalContainerRef] preventDefault - 스크롤 영역에서의 클릭');
|
||||||
} else {
|
} else {
|
||||||
// VideoPlayer 영역이면 이벤트를 전파시켜 click이 정상 발생하도록
|
|
||||||
console.log('✅ [normalContainerRef] 이벤트 전파 허용 - VideoPlayer 영역');
|
console.log('✅ [normalContainerRef] 이벤트 전파 허용 - VideoPlayer 영역');
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
{renderVideoPlayer()}
|
)}
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</ContainerComponent>
|
</ContainerComponent>
|
||||||
|
|
||||||
{/* 전체화면 container (Portal) */}
|
{/* 전체화면 container (Portal) */}
|
||||||
{isPlaying && isFullscreen && ReactDOM.createPortal(
|
{isPlaying &&
|
||||||
<SpotlightContainer
|
isFullscreen &&
|
||||||
spotlightRestrict="self-only"
|
ReactDOM.createPortal(
|
||||||
spotlightId="product-video-v2-fullscreen-portal"
|
<SpotlightContainer
|
||||||
onMouseDownCapture={handleFullscreenContainerMouseDownCapture}
|
spotlightRestrict="self-only"
|
||||||
onKeyDown={handleFullscreenKeyDown}
|
spotlightId="product-video-v2-fullscreen-portal"
|
||||||
// onClick={handleFullscreenClick}
|
onMouseDownCapture={handleFullscreenContainerMouseDownCapture}
|
||||||
>
|
onKeyDown={handleFullscreenKeyDown}
|
||||||
<div
|
// onClick={handleFullscreenClick}
|
||||||
ref={fullscreenContainerRef}
|
|
||||||
className={css.fullscreenContainer}
|
|
||||||
>
|
>
|
||||||
{renderVideoPlayer()}
|
<div ref={fullscreenContainerRef} className={css.fullscreenContainer} />
|
||||||
</div>
|
</SpotlightContainer>,
|
||||||
</SpotlightContainer>,
|
document.body
|
||||||
document.body
|
)}
|
||||||
)}
|
|
||||||
|
{renderVideoPlayer()}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user