[251112] feat: ProductVideoV2 fullScreen ovelay toggle
🕐 커밋 시간: 2025. 11. 12. 17:35:22 📊 변경 통계: • 총 파일: 2개 • 추가: +57줄 • 삭제: -28줄 📝 수정된 파일: ~ com.twin.app.shoptime/src/components/VideoPlayer/MediaPlayer.jsx ~ com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v2.jsx 🔧 주요 변경 내용: • UI 컴포넌트 아키텍처 개선 • 소규모 기능 개선
This commit is contained in:
@@ -65,7 +65,7 @@ const isEnter = is('enter');
|
|||||||
const isLeft = is('left');
|
const isLeft = is('left');
|
||||||
const isRight = is('right');
|
const isRight = is('right');
|
||||||
|
|
||||||
const jumpBackKeyCode = 3
|
const jumpBackKeyCode = 3;
|
||||||
const jumpForwardKeyCode = 39;
|
const jumpForwardKeyCode = 39;
|
||||||
const controlsHandleAboveSelectionKeys = [13, 16777221, jumpBackKeyCode, jumpForwardKeyCode];
|
const controlsHandleAboveSelectionKeys = [13, 16777221, jumpBackKeyCode, jumpForwardKeyCode];
|
||||||
const getControlsHandleAboveHoldConfig = ({ frequency, time }) => ({
|
const getControlsHandleAboveHoldConfig = ({ frequency, time }) => ({
|
||||||
@@ -706,6 +706,7 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
reactPlayerConfig: PropTypes.any, //for ReactPlayer
|
reactPlayerConfig: PropTypes.any, //for ReactPlayer
|
||||||
qrCurrentItem: PropTypes.any,
|
qrCurrentItem: PropTypes.any,
|
||||||
modalScale: PropTypes.number,
|
modalScale: PropTypes.number,
|
||||||
|
notifyOnClickWhenNotModal: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
static contextType = FloatingLayerContext;
|
static contextType = FloatingLayerContext;
|
||||||
@@ -718,6 +719,7 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
jumpDelay: 200,
|
jumpDelay: 200,
|
||||||
mediaControlsComponent: MediaControls,
|
mediaControlsComponent: MediaControls,
|
||||||
miniFeedbackHideDelay: 2000,
|
miniFeedbackHideDelay: 2000,
|
||||||
|
notifyOnClickWhenNotModal: false,
|
||||||
playbackRateHash: {
|
playbackRateHash: {
|
||||||
fastForward: ['2', '4', '8', '16'],
|
fastForward: ['2', '4', '8', '16'],
|
||||||
rewind: ['-2', '-4', '-8', '-16'],
|
rewind: ['-2', '-4', '-8', '-16'],
|
||||||
@@ -1429,7 +1431,13 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 🎬 실시간 재생상태 로깅
|
// 🎬 실시간 재생상태 로깅
|
||||||
if (ev.type === 'onUpdate' || ev.type === 'onPlay' || ev.type === 'onPause' || ev.type === 'onStart' || ev.type === 'onLoadStart') {
|
if (
|
||||||
|
ev.type === 'onUpdate' ||
|
||||||
|
ev.type === 'onPlay' ||
|
||||||
|
ev.type === 'onPause' ||
|
||||||
|
ev.type === 'onStart' ||
|
||||||
|
ev.type === 'onLoadStart'
|
||||||
|
) {
|
||||||
console.log('🎬 [MediaPlayer] 재생상태 실시간 로깅', {
|
console.log('🎬 [MediaPlayer] 재생상태 실시간 로깅', {
|
||||||
eventType: ev.type,
|
eventType: ev.type,
|
||||||
currentTime: el.currentTime,
|
currentTime: el.currentTime,
|
||||||
@@ -1933,14 +1941,20 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
//
|
//
|
||||||
// Player Interaction events
|
// Player Interaction events
|
||||||
//
|
//
|
||||||
onVideoClick = () => {
|
onVideoClick = (ev) => {
|
||||||
console.log('[MediaPlayer] onVideoClick 호출');
|
console.log('[MediaPlayer] onVideoClick 호출');
|
||||||
|
|
||||||
// modal 상태일 때 외부 onClick 핸들러가 있으면 호출
|
const shouldNotify =
|
||||||
if (this.props.panelInfo?.modal && this.props.onClick) {
|
(this.props.panelInfo?.modal || this.props.notifyOnClickWhenNotModal) &&
|
||||||
console.log('[MediaPlayer] modal에서 onClick 호출');
|
typeof this.props.onClick === 'function';
|
||||||
this.props.onClick();
|
|
||||||
return;
|
if (shouldNotify) {
|
||||||
|
console.log('[MediaPlayer] 외부 onClick 콜백 호출');
|
||||||
|
this.props.onClick(ev);
|
||||||
|
|
||||||
|
if (this.props.panelInfo?.modal && !this.props.notifyOnClickWhenNotModal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MediaPanel에서는 controls 토글만
|
// MediaPanel에서는 controls 토글만
|
||||||
|
|||||||
@@ -86,6 +86,11 @@ export function ProductVideoV2({
|
|||||||
const fullscreenContainerRef = useRef(null);
|
const fullscreenContainerRef = useRef(null);
|
||||||
const videoPortalHostRef = useRef(null);
|
const videoPortalHostRef = useRef(null);
|
||||||
|
|
||||||
|
const isFullscreenRef = useRef(isFullscreen);
|
||||||
|
useEffect(() => {
|
||||||
|
isFullscreenRef.current = isFullscreen;
|
||||||
|
}, [isFullscreen]);
|
||||||
|
|
||||||
const ensurePortalHost = useCallback(() => {
|
const ensurePortalHost = useCallback(() => {
|
||||||
if (!videoPortalHostRef.current && typeof document !== 'undefined') {
|
if (!videoPortalHostRef.current && typeof document !== 'undefined') {
|
||||||
const host = document.createElement('div');
|
const host = document.createElement('div');
|
||||||
@@ -373,9 +378,18 @@ export function ProductVideoV2({
|
|||||||
// FullScreen 모드에서의 MediaPlayer Click 핸들러
|
// FullScreen 모드에서의 MediaPlayer Click 핸들러
|
||||||
const handleVideoPlayerClick = useCallback(
|
const handleVideoPlayerClick = useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
|
const fullscreenNow = isFullscreenRef.current;
|
||||||
|
|
||||||
|
console.log('🎥 [ProductVideoV2] handleVideoPlayerClick 감지됨', {
|
||||||
|
isPlaying,
|
||||||
|
isFullscreen: fullscreenNow,
|
||||||
|
eventType: e?.type,
|
||||||
|
target: e?.target?.className,
|
||||||
|
});
|
||||||
|
|
||||||
if (!isPlaying) return;
|
if (!isPlaying) return;
|
||||||
|
|
||||||
if (!isFullscreen) {
|
if (!fullscreenNow) {
|
||||||
e.preventDefault?.();
|
e.preventDefault?.();
|
||||||
e.stopPropagation?.();
|
e.stopPropagation?.();
|
||||||
toggleFullscreen();
|
toggleFullscreen();
|
||||||
@@ -627,6 +641,8 @@ export function ProductVideoV2({
|
|||||||
// VideoContainer의 모든 클릭 감시 (Hooks는 early return 전에 정의되어야 함)
|
// VideoContainer의 모든 클릭 감시 (Hooks는 early return 전에 정의되어야 함)
|
||||||
const handleVideoContainerClick = useCallback(
|
const handleVideoContainerClick = useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
|
if (isFullscreen) return;
|
||||||
|
|
||||||
console.log('🎥 [ProductVideoV2] videoContainer onClick 감지됨', {
|
console.log('🎥 [ProductVideoV2] videoContainer onClick 감지됨', {
|
||||||
isPlaying,
|
isPlaying,
|
||||||
isFullscreen,
|
isFullscreen,
|
||||||
@@ -638,7 +654,7 @@ export function ProductVideoV2({
|
|||||||
timestamp: new Date().getTime(),
|
timestamp: new Date().getTime(),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isPlaying && !isFullscreen) {
|
if (isPlaying) {
|
||||||
console.log('🎥 [ProductVideoV2] videoContainer 클릭 → 직접 전체화면 토글 실행', {
|
console.log('🎥 [ProductVideoV2] videoContainer 클릭 → 직접 전체화면 토글 실행', {
|
||||||
direct: true,
|
direct: true,
|
||||||
});
|
});
|
||||||
@@ -651,20 +667,22 @@ export function ProductVideoV2({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const containerProps = useMemo(() => {
|
const containerProps = useMemo(() => {
|
||||||
const baseProps = {
|
if (isPlaying && !isFullscreen) {
|
||||||
onClick: handleVideoContainerClick,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isPlaying) {
|
|
||||||
return {
|
return {
|
||||||
...baseProps,
|
onClick: handleVideoContainerClick,
|
||||||
spotlightId: 'product-video-v2-playing',
|
spotlightId: 'product-video-v2-playing',
|
||||||
onKeyDown: handleContainerKeyDown, // 일반 모드: 컨테이너에서 직접 처리
|
onKeyDown: handleContainerKeyDown,
|
||||||
onSpotlightDown: handleSpotlightDown, // 일반 재생에서도 Down 키 동작
|
onSpotlightDown: handleSpotlightDown,
|
||||||
};
|
};
|
||||||
} else {
|
|
||||||
return baseProps;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isFullscreen) {
|
||||||
|
return {
|
||||||
|
onClick: handleVideoContainerClick,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
}, [
|
}, [
|
||||||
isFullscreen,
|
isFullscreen,
|
||||||
isPlaying,
|
isPlaying,
|
||||||
@@ -676,6 +694,8 @@ export function ProductVideoV2({
|
|||||||
// ⚠️ 간단한 해결책: 비디오 영역 클릭 시 직접 동작 실행
|
// ⚠️ 간단한 해결책: 비디오 영역 클릭 시 직접 동작 실행
|
||||||
const handleContainerClickFallback = useCallback(
|
const handleContainerClickFallback = useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
|
if (isFullscreen) return;
|
||||||
|
|
||||||
const isThumbnailArea =
|
const isThumbnailArea =
|
||||||
e.target?.closest('[class*="videoThumbnail"]') ||
|
e.target?.closest('[class*="videoThumbnail"]') ||
|
||||||
e.target?.closest('[class*="playButton"]');
|
e.target?.closest('[class*="playButton"]');
|
||||||
@@ -683,14 +703,12 @@ export function ProductVideoV2({
|
|||||||
const isVideoPlayerArea =
|
const isVideoPlayerArea =
|
||||||
e.target?.closest('[class*="videoPlayer"]') || 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) {
|
||||||
if (isVideoPlayerArea && isPlaying && !isFullscreen) {
|
|
||||||
console.log('🎬 [handleContainerClickFallback] 비디오 클릭 → 전체화면 토글');
|
console.log('🎬 [handleContainerClickFallback] 비디오 클릭 → 전체화면 토글');
|
||||||
toggleFullscreen();
|
toggleFullscreen();
|
||||||
}
|
}
|
||||||
@@ -705,6 +723,8 @@ export function ProductVideoV2({
|
|||||||
// 비디오 영역인지 확인
|
// 비디오 영역인지 확인
|
||||||
const isVideoArea = e.currentTarget === containerRef.current;
|
const isVideoArea = e.currentTarget === containerRef.current;
|
||||||
|
|
||||||
|
console.log('####[ProductVideoV2 Container] isFullscreen:', isFullscreen);
|
||||||
|
|
||||||
console.log('🔓 [ProductVideoV2 Container] onMouseDownCapture - TScrollerDetail 차단 우회', {
|
console.log('🔓 [ProductVideoV2 Container] onMouseDownCapture - TScrollerDetail 차단 우회', {
|
||||||
isVideoArea,
|
isVideoArea,
|
||||||
target: e.target?.className,
|
target: e.target?.className,
|
||||||
@@ -773,7 +793,7 @@ export function ProductVideoV2({
|
|||||||
onTouchMove={handleUserActivity}
|
onTouchMove={handleUserActivity}
|
||||||
onWheel={handleUserActivity}
|
onWheel={handleUserActivity}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
console.log('🎬 [videoPlayerWrapper] onClick 실행됨', {
|
console.log('<<<<<<< [videoPlayerWrapper] onClick 실행됨', {
|
||||||
isFullscreen,
|
isFullscreen,
|
||||||
isPlaying,
|
isPlaying,
|
||||||
eventType: e.type,
|
eventType: e.type,
|
||||||
@@ -813,6 +833,7 @@ export function ProductVideoV2({
|
|||||||
onEnded={handleVideoEnded}
|
onEnded={handleVideoEnded}
|
||||||
onBackButton={handleBackButton}
|
onBackButton={handleBackButton}
|
||||||
onClick={handleVideoPlayerClick}
|
onClick={handleVideoPlayerClick}
|
||||||
|
notifyOnClickWhenNotModal
|
||||||
noAutoPlay={false}
|
noAutoPlay={false}
|
||||||
noAutoShowMediaControls={!isFullscreen}
|
noAutoShowMediaControls={!isFullscreen}
|
||||||
autoCloseTimeout={5000}
|
autoCloseTimeout={5000}
|
||||||
@@ -858,8 +879,8 @@ export function ProductVideoV2({
|
|||||||
{...containerProps}
|
{...containerProps}
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
className={`${css.videoContainer} ${isFullscreen ? css.hideWhileFullscreen : ''}`}
|
className={`${css.videoContainer} ${isFullscreen ? css.hideWhileFullscreen : ''}`}
|
||||||
onMouseDownCapture={handleContainerMouseDownCapture}
|
onMouseDownCapture={!isFullscreen ? handleContainerMouseDownCapture : undefined}
|
||||||
onClick={handleContainerClickFallback}
|
onClick={!isFullscreen ? handleContainerClickFallback : undefined}
|
||||||
>
|
>
|
||||||
{!isPlaying ? (
|
{!isPlaying ? (
|
||||||
// 썸네일 + 재생 버튼 표시
|
// 썸네일 + 재생 버튼 표시
|
||||||
@@ -892,34 +913,44 @@ export function ProductVideoV2({
|
|||||||
<div
|
<div
|
||||||
ref={normalContainerRef}
|
ref={normalContainerRef}
|
||||||
className={css.normalPlayerContainer}
|
className={css.normalPlayerContainer}
|
||||||
onClick={(e) => {
|
onClick={
|
||||||
console.log('🎬 [normalContainerRef] onClick 실행됨', {
|
!isFullscreen
|
||||||
isPlaying,
|
? (e) => {
|
||||||
isFullscreen,
|
console.log('🎬 [normalContainerRef] onClick 실행됨', {
|
||||||
eventType: e.type,
|
isPlaying,
|
||||||
target: e.target?.className,
|
isFullscreen,
|
||||||
});
|
eventType: e.type,
|
||||||
handleVideoPlayerClick(e);
|
target: e.target?.className,
|
||||||
}}
|
});
|
||||||
onMouseDownCapture={(e) => {
|
handleVideoPlayerClick(e);
|
||||||
const isVideoPlayerArea =
|
}
|
||||||
e.target?.closest('[class*="videoPlayer"]') ||
|
: undefined
|
||||||
e.target?.closest('[class*="VideoPlayer"]');
|
}
|
||||||
|
onMouseDownCapture={
|
||||||
|
!isFullscreen
|
||||||
|
? (e) => {
|
||||||
|
const isVideoPlayerArea =
|
||||||
|
e.target?.closest('[class*="videoPlayer"]') ||
|
||||||
|
e.target?.closest('[class*="VideoPlayer"]');
|
||||||
|
|
||||||
console.log('🎬 [normalContainerRef] onMouseDownCapture 실행됨', {
|
console.log('🎬 [normalContainerRef] onMouseDownCapture 실행됨', {
|
||||||
isPlaying,
|
isPlaying,
|
||||||
isFullscreen,
|
isFullscreen,
|
||||||
isVideoPlayerArea,
|
isVideoPlayerArea,
|
||||||
target: e.target?.className,
|
target: e.target?.className,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!isVideoPlayerArea) {
|
if (!isVideoPlayerArea) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
console.log('🛑 [normalContainerRef] preventDefault - 스크롤 영역에서의 클릭');
|
console.log(
|
||||||
} else {
|
'🛑 [normalContainerRef] preventDefault - 스크롤 영역에서의 클릭'
|
||||||
console.log('✅ [normalContainerRef] 이벤트 전파 허용 - VideoPlayer 영역');
|
);
|
||||||
}
|
} else {
|
||||||
}}
|
console.log('✅ [normalContainerRef] 이벤트 전파 허용 - VideoPlayer 영역');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</ContainerComponent>
|
</ContainerComponent>
|
||||||
|
|||||||
Reference in New Issue
Block a user