[251111] fix: MediaPlayer FullScreen Overlay Toggle
🕐 커밋 시간: 2025. 11. 11. 19:08:01 📊 변경 통계: • 총 파일: 6개 • 추가: +71줄 • 삭제: -70줄 📁 추가된 파일: + com.twin.app.shoptime/src/actions/mediaOverlayActions.js + com.twin.app.shoptime/src/middleware/mediaAutoCloseMiddleware.js + com.twin.app.shoptime/src/reducers/mediaOverlayReducer.js 📝 수정된 파일: ~ com.twin.app.shoptime/src/components/VideoPlayer/MediaPlayer.v2.jsx ~ com.twin.app.shoptime/src/store/store.js ~ com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v2.jsx 🔧 주요 변경 내용: • UI 컴포넌트 아키텍처 개선 • 핵심 비즈니스 로직 개선 • 소규모 기능 개선 • 코드 정리 및 최적화 • 모듈 구조 개선
This commit is contained in:
70
com.twin.app.shoptime/src/actions/mediaOverlayActions.js
Normal file
70
com.twin.app.shoptime/src/actions/mediaOverlayActions.js
Normal file
@@ -0,0 +1,70 @@
|
||||
// Media Overlay Actions - Redux를 통한 MediaPanel/MediaPlayer overlay 제어
|
||||
// 3 Layer 구조(MediaPanel + MediaPlayer) 전용 overlay 상태 관리
|
||||
|
||||
export const MEDIA_OVERLAY_ACTIONS = {
|
||||
// Media Controls 표시/숨김
|
||||
SET_MEDIA_CONTROLS_SHOW: 'SET_MEDIA_CONTROLS_SHOW',
|
||||
SET_MEDIA_CONTROLS_HIDE: 'SET_MEDIA_CONTROLS_HIDE',
|
||||
SET_MEDIA_CONTROLS_TOGGLE: 'SET_MEDIA_CONTROLS_TOGGLE',
|
||||
|
||||
// Media AutoClose 제어
|
||||
START_MEDIA_AUTO_CLOSE: 'START_MEDIA_AUTO_CLOSE',
|
||||
STOP_MEDIA_AUTO_CLOSE: 'STOP_MEDIA_AUTO_CLOSE',
|
||||
RESET_MEDIA_AUTO_CLOSE: 'RESET_MEDIA_AUTO_CLOSE',
|
||||
UPDATE_MEDIA_AUTO_CLOSE_TIMEOUT: 'UPDATE_MEDIA_AUTO_CLOSE_TIMEOUT',
|
||||
|
||||
// Media Overlay 세부 제어
|
||||
SET_MEDIA_OVERLAY_VISIBILITY: 'SET_MEDIA_OVERLAY_VISIBILITY',
|
||||
};
|
||||
|
||||
// Media Controls 표시/숨김 액션
|
||||
export const setMediaControlShow = () => ({
|
||||
type: MEDIA_OVERLAY_ACTIONS.SET_MEDIA_CONTROLS_SHOW,
|
||||
payload: { timestamp: Date.now() },
|
||||
});
|
||||
|
||||
export const setMediaControlHide = () => ({
|
||||
type: MEDIA_OVERLAY_ACTIONS.SET_MEDIA_CONTROLS_HIDE,
|
||||
payload: { timestamp: Date.now() },
|
||||
});
|
||||
|
||||
export const setMediaControlToggle = () => ({
|
||||
type: MEDIA_OVERLAY_ACTIONS.SET_MEDIA_CONTROLS_TOGGLE,
|
||||
payload: { timestamp: Date.now() },
|
||||
});
|
||||
|
||||
// Media AutoClose 타이머 제어 액션
|
||||
export const startMediaAutoClose = (timeout = 3000) => ({
|
||||
type: MEDIA_OVERLAY_ACTIONS.START_MEDIA_AUTO_CLOSE,
|
||||
payload: {
|
||||
timeout,
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
});
|
||||
|
||||
export const stopMediaAutoClose = () => ({
|
||||
type: MEDIA_OVERLAY_ACTIONS.STOP_MEDIA_AUTO_CLOSE,
|
||||
payload: { timestamp: Date.now() },
|
||||
});
|
||||
|
||||
export const resetMediaAutoClose = () => ({
|
||||
type: MEDIA_OVERLAY_ACTIONS.RESET_MEDIA_AUTO_CLOSE,
|
||||
payload: { timestamp: Date.now() },
|
||||
});
|
||||
|
||||
export const updateMediaAutoCloseTimeout = (timeout) => ({
|
||||
type: MEDIA_OVERLAY_ACTIONS.UPDATE_MEDIA_AUTO_CLOSE_TIMEOUT,
|
||||
payload: {
|
||||
timeout,
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
});
|
||||
|
||||
// Media Overlay 세부 제어 액션
|
||||
export const setMediaOverlayVisibility = (overlayConfig) => ({
|
||||
type: MEDIA_OVERLAY_ACTIONS.SET_MEDIA_OVERLAY_VISIBILITY,
|
||||
payload: {
|
||||
...overlayConfig,
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
});
|
||||
@@ -26,6 +26,15 @@ import { MediaSlider, Times, secondsToTime } from '../MediaPlayer';
|
||||
import Overlay from './Overlay';
|
||||
import Media from './Media';
|
||||
import TReactPlayer from './TReactPlayer';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import {
|
||||
setMediaControlShow,
|
||||
setMediaControlHide,
|
||||
setMediaControlToggle,
|
||||
startMediaAutoClose,
|
||||
stopMediaAutoClose,
|
||||
resetMediaAutoClose
|
||||
} from '../../actions/mediaOverlayActions';
|
||||
|
||||
import css from './MediaPlayer.module.less';
|
||||
|
||||
@@ -68,6 +77,10 @@ const getDurFmt = () => {
|
||||
* MediaPlayer.v2 컴포넌트
|
||||
*/
|
||||
const MediaPlayerV2 = forwardRef((props, ref) => {
|
||||
// Redux hooks
|
||||
const dispatch = useDispatch();
|
||||
const mediaOverlayState = useSelector((state) => state.mediaOverlay);
|
||||
|
||||
const {
|
||||
// 비디오 소스
|
||||
src,
|
||||
@@ -123,11 +136,13 @@ const MediaPlayerV2 = forwardRef((props, ref) => {
|
||||
const [paused, setPaused] = useState(!autoPlay);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
const [controlsVisible, setControlsVisible] = useState(false);
|
||||
const [sourceUnavailable, setSourceUnavailable] = useState(true);
|
||||
const [proportionLoaded, setProportionLoaded] = useState(0);
|
||||
const [proportionPlayed, setProportionPlayed] = useState(0);
|
||||
|
||||
// controlsVisible은 Redux mediaOverlay 상태에서 가져오기
|
||||
const controlsVisible = mediaOverlayState?.controls?.visible || false;
|
||||
|
||||
// ========== Refs ==========
|
||||
const videoRef = useRef(null);
|
||||
const playerRef = useRef(null);
|
||||
@@ -267,32 +282,25 @@ const MediaPlayerV2 = forwardRef((props, ref) => {
|
||||
const showControls = useCallback((timeout = 3000) => {
|
||||
if (disabled || isModal) return;
|
||||
|
||||
setControlsVisible(true);
|
||||
|
||||
// timeout 후 자동 숨김 (기본 3초, Modal 전환 시 10초)
|
||||
if (controlsTimeoutRef.current) {
|
||||
clearTimeout(controlsTimeoutRef.current);
|
||||
}
|
||||
controlsTimeoutRef.current = setTimeout(() => {
|
||||
setControlsVisible(false);
|
||||
}, timeout);
|
||||
}, [disabled, isModal]);
|
||||
console.log('🎬 [MediaPlayer.v2] showControls called, dispatching setMediaControlShow');
|
||||
dispatch(setMediaControlShow());
|
||||
dispatch(startMediaAutoClose(timeout));
|
||||
}, [disabled, isModal, dispatch]);
|
||||
|
||||
const hideControls = useCallback(() => {
|
||||
setControlsVisible(false);
|
||||
console.log('🎬 [MediaPlayer.v2] hideControls called, dispatching setMediaControlHide');
|
||||
dispatch(setMediaControlHide());
|
||||
dispatch(stopMediaAutoClose());
|
||||
if (controlsTimeoutRef.current) {
|
||||
clearTimeout(controlsTimeoutRef.current);
|
||||
controlsTimeoutRef.current = null;
|
||||
}
|
||||
}, []);
|
||||
}, [dispatch]);
|
||||
|
||||
const toggleControls = useCallback(() => {
|
||||
if (controlsVisible) {
|
||||
hideControls();
|
||||
} else {
|
||||
showControls();
|
||||
}
|
||||
}, [controlsVisible, hideControls, showControls]);
|
||||
console.log('🎬 [MediaPlayer.v2] toggleControls called, dispatching setMediaControlToggle');
|
||||
dispatch(setMediaControlToggle());
|
||||
}, [dispatch]);
|
||||
|
||||
// ========== Playback Control ==========
|
||||
const play = useCallback(() => {
|
||||
@@ -420,8 +428,10 @@ const MediaPlayerV2 = forwardRef((props, ref) => {
|
||||
if (controlsTimeoutRef.current) {
|
||||
clearTimeout(controlsTimeoutRef.current);
|
||||
}
|
||||
// MediaPlayer 언마운트 시 Redux 상태 정리
|
||||
dispatch(stopMediaAutoClose());
|
||||
};
|
||||
}, []);
|
||||
}, [dispatch]);
|
||||
|
||||
// ========== Imperative Handle (API) ==========
|
||||
useImperativeHandle(ref, () => ({
|
||||
|
||||
139
com.twin.app.shoptime/src/middleware/mediaAutoCloseMiddleware.js
Normal file
139
com.twin.app.shoptime/src/middleware/mediaAutoCloseMiddleware.js
Normal file
@@ -0,0 +1,139 @@
|
||||
// Media Auto Close Middleware - Redux를 통한 MediaPanel/MediaPlayer controls 자동 숨김 타이머 관리
|
||||
// 3 Layer 구조(MediaPanel + MediaPlayer) 전용 autoClose 기능 제공
|
||||
|
||||
import {
|
||||
MEDIA_OVERLAY_ACTIONS,
|
||||
setMediaControlHide,
|
||||
stopMediaAutoClose,
|
||||
} from '../actions/mediaOverlayActions';
|
||||
|
||||
// 타이머 저장소
|
||||
let mediaAutoCloseTimer = null;
|
||||
|
||||
/**
|
||||
* Media Auto Close Middleware
|
||||
* - START_MEDIA_AUTO_CLOSE: 타이머 시작
|
||||
* - STOP_MEDIA_AUTO_CLOSE, SET_MEDIA_CONTROLS_HIDE: 타이머 중지
|
||||
* - SET_MEDIA_CONTROLS_TOGGLE: Controls 표시 상태로 토글되면 타이머 시작
|
||||
* - 사용자 활동 감지 시: 타이머 리셋
|
||||
*/
|
||||
export const mediaAutoCloseMiddleware = (store) => (next) => (action) => {
|
||||
const result = next(action);
|
||||
|
||||
// 현재 상태 획득
|
||||
const state = store.getState();
|
||||
const mediaOverlayState = state.mediaOverlay;
|
||||
|
||||
// Action별 처리
|
||||
switch (action.type) {
|
||||
case MEDIA_OVERLAY_ACTIONS.START_MEDIA_AUTO_CLOSE: {
|
||||
// 기존 타이머 정리
|
||||
clearTimeout(mediaAutoCloseTimer);
|
||||
|
||||
const timeout = action.payload?.timeout || 3000;
|
||||
|
||||
console.log('[mediaAutoCloseMiddleware] Starting media auto-close timer:', timeout, 'ms');
|
||||
|
||||
// 새 타이머 시작
|
||||
mediaAutoCloseTimer = setTimeout(() => {
|
||||
console.log('[mediaAutoCloseMiddleware] Media auto-close timeout reached - hiding media controls');
|
||||
store.dispatch(setMediaControlHide());
|
||||
mediaAutoCloseTimer = null;
|
||||
}, timeout);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case MEDIA_OVERLAY_ACTIONS.STOP_MEDIA_AUTO_CLOSE: {
|
||||
if (mediaAutoCloseTimer) {
|
||||
console.log('[mediaAutoCloseMiddleware] Stopping media auto-close timer');
|
||||
clearTimeout(mediaAutoCloseTimer);
|
||||
mediaAutoCloseTimer = null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MEDIA_OVERLAY_ACTIONS.SET_MEDIA_CONTROLS_HIDE: {
|
||||
// Media Controls 숨김 시 타이머도 중지
|
||||
if (mediaAutoCloseTimer) {
|
||||
clearTimeout(mediaAutoCloseTimer);
|
||||
mediaAutoCloseTimer = null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MEDIA_OVERLAY_ACTIONS.SET_MEDIA_CONTROLS_TOGGLE: {
|
||||
// Media Controls가 표시 상태로 토글되면 타이머 시작
|
||||
if (!mediaOverlayState.controls.visible) {
|
||||
// 다음 상태에서 visible이 true가 될 것이므로
|
||||
// 타이머를 시작해야 함
|
||||
clearTimeout(mediaAutoCloseTimer);
|
||||
|
||||
const timeout = mediaOverlayState.autoClose.timeout || 3000;
|
||||
console.log('[mediaAutoCloseMiddleware] Media controls toggled to visible - starting timer:', timeout, 'ms');
|
||||
|
||||
mediaAutoCloseTimer = setTimeout(() => {
|
||||
console.log('[mediaAutoCloseMiddleware] Toggle media auto-close timeout reached - hiding media controls');
|
||||
store.dispatch(setMediaControlHide());
|
||||
mediaAutoCloseTimer = null;
|
||||
}, timeout);
|
||||
} else {
|
||||
// Media Controls 숨김 상태로 토글되면 타이머 중지
|
||||
if (mediaAutoCloseTimer) {
|
||||
clearTimeout(mediaAutoCloseTimer);
|
||||
mediaAutoCloseTimer = null;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MEDIA_OVERLAY_ACTIONS.RESET_MEDIA_AUTO_CLOSE: {
|
||||
// 타이머 리셋 (사용자 활동 감지)
|
||||
clearTimeout(mediaAutoCloseTimer);
|
||||
|
||||
if (mediaOverlayState.controls.visible) {
|
||||
const timeout = mediaOverlayState.autoClose.timeout || 3000;
|
||||
console.log('[mediaAutoCloseMiddleware] Resetting media auto-close timer:', timeout, 'ms');
|
||||
|
||||
mediaAutoCloseTimer = setTimeout(() => {
|
||||
console.log('[mediaAutoCloseMiddleware] Reset media auto-close timeout reached - hiding media controls');
|
||||
store.dispatch(setMediaControlHide());
|
||||
mediaAutoCloseTimer = null;
|
||||
}, timeout);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MEDIA_OVERLAY_ACTIONS.SET_MEDIA_CONTROLS_SHOW: {
|
||||
// Media Controls 표시 시 타이머 시작
|
||||
clearTimeout(mediaAutoCloseTimer);
|
||||
|
||||
const timeout = mediaOverlayState.autoClose.timeout || 3000;
|
||||
console.log('[mediaAutoCloseMiddleware] Media controls shown - starting timer:', timeout, 'ms');
|
||||
|
||||
mediaAutoCloseTimer = setTimeout(() => {
|
||||
console.log('[mediaAutoCloseMiddleware] Show media auto-close timeout reached - hiding media controls');
|
||||
store.dispatch(setMediaControlHide());
|
||||
mediaAutoCloseTimer = null;
|
||||
}, timeout);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Cleanup 함수 (필요시 호출)
|
||||
* - 앱 종료 시 타이머 정리
|
||||
*/
|
||||
export const cleanupMediaAutoCloseMiddleware = () => {
|
||||
if (mediaAutoCloseTimer) {
|
||||
clearTimeout(mediaAutoCloseTimer);
|
||||
mediaAutoCloseTimer = null;
|
||||
}
|
||||
};
|
||||
168
com.twin.app.shoptime/src/reducers/mediaOverlayReducer.js
Normal file
168
com.twin.app.shoptime/src/reducers/mediaOverlayReducer.js
Normal file
@@ -0,0 +1,168 @@
|
||||
// Media Overlay Reducer - 3 Layer 구조(MediaPanel + MediaPlayer)용 overlay 상태 관리
|
||||
import { MEDIA_OVERLAY_ACTIONS } from '../actions/mediaOverlayActions';
|
||||
|
||||
export const initialState = {
|
||||
// Media Overlay Controls 상태
|
||||
controls: {
|
||||
visible: false, // media controls 표시 여부
|
||||
sliderVisible: false, // 슬라이더 표시 여부
|
||||
titleVisible: true, // 제목 표시 여부
|
||||
feedbackVisible: false, // 피드백 표시 여부
|
||||
},
|
||||
|
||||
// Media AutoClose 타이머 제어
|
||||
autoClose: {
|
||||
enabled: true, // autoClose 활성화 여부
|
||||
timeout: 3000, // 타임아웃 시간 (ms)
|
||||
remainingTime: 0, // 남은 시간 (ms)
|
||||
active: false, // 타이머 실행 중 여부
|
||||
},
|
||||
|
||||
// 미디어 정보
|
||||
mediaInfo: {
|
||||
url: '',
|
||||
title: '',
|
||||
thumbnailUrl: '',
|
||||
isYoutube: false,
|
||||
},
|
||||
|
||||
// 상태 변경 기록 (디버깅용)
|
||||
timestamp: null,
|
||||
lastAction: null,
|
||||
};
|
||||
|
||||
/**
|
||||
* Reducer handlers 맵
|
||||
*/
|
||||
const handlers = {
|
||||
[MEDIA_OVERLAY_ACTIONS.SET_MEDIA_CONTROLS_SHOW]: (state, action) => ({
|
||||
...state,
|
||||
controls: {
|
||||
...state.controls,
|
||||
visible: true,
|
||||
sliderVisible: true,
|
||||
titleVisible: true,
|
||||
},
|
||||
autoClose: {
|
||||
...state.autoClose,
|
||||
active: true, // Controls 표시 시 autoClose 시작
|
||||
},
|
||||
timestamp: action.payload.timestamp,
|
||||
lastAction: MEDIA_OVERLAY_ACTIONS.SET_MEDIA_CONTROLS_SHOW,
|
||||
}),
|
||||
|
||||
[MEDIA_OVERLAY_ACTIONS.SET_MEDIA_CONTROLS_HIDE]: (state, action) => ({
|
||||
...state,
|
||||
controls: {
|
||||
...state.controls,
|
||||
visible: false,
|
||||
sliderVisible: false,
|
||||
feedbackVisible: false,
|
||||
},
|
||||
autoClose: {
|
||||
...state.autoClose,
|
||||
active: false, // Controls 숨김 시 autoClose 중지
|
||||
remainingTime: 0,
|
||||
},
|
||||
timestamp: action.payload.timestamp,
|
||||
lastAction: MEDIA_OVERLAY_ACTIONS.SET_MEDIA_CONTROLS_HIDE,
|
||||
}),
|
||||
|
||||
[MEDIA_OVERLAY_ACTIONS.SET_MEDIA_CONTROLS_TOGGLE]: (state, action) => {
|
||||
const isCurrentlyVisible = state.controls.visible;
|
||||
const newState = {
|
||||
...state,
|
||||
controls: {
|
||||
...state.controls,
|
||||
visible: !isCurrentlyVisible,
|
||||
sliderVisible: !isCurrentlyVisible,
|
||||
titleVisible: !isCurrentlyVisible || state.controls.titleVisible,
|
||||
},
|
||||
autoClose: {
|
||||
...state.autoClose,
|
||||
active: !isCurrentlyVisible, // 표시 상태로 토글되면 autoClose 시작
|
||||
},
|
||||
timestamp: action.payload.timestamp,
|
||||
lastAction: MEDIA_OVERLAY_ACTIONS.SET_MEDIA_CONTROLS_TOGGLE,
|
||||
};
|
||||
|
||||
console.log('🔄 [mediaOverlayReducer.SET_MEDIA_CONTROLS_TOGGLE] 상태 변화');
|
||||
console.log('🔄 [mediaOverlayReducer.SET_MEDIA_CONTROLS_TOGGLE] 이전 visible:', isCurrentlyVisible);
|
||||
console.log('🔄 [mediaOverlayReducer.SET_MEDIA_CONTROLS_TOGGLE] 이후 visible:', newState.controls.visible);
|
||||
console.log('🔄 [mediaOverlayReducer.SET_MEDIA_CONTROLS_TOGGLE] 전체 상태:', newState);
|
||||
|
||||
return newState;
|
||||
},
|
||||
|
||||
[MEDIA_OVERLAY_ACTIONS.START_MEDIA_AUTO_CLOSE]: (state, action) => ({
|
||||
...state,
|
||||
autoClose: {
|
||||
...state.autoClose,
|
||||
enabled: true,
|
||||
timeout: action.payload.timeout || state.autoClose.timeout,
|
||||
remainingTime: action.payload.timeout || state.autoClose.timeout,
|
||||
active: true,
|
||||
},
|
||||
timestamp: action.payload.timestamp,
|
||||
lastAction: MEDIA_OVERLAY_ACTIONS.START_MEDIA_AUTO_CLOSE,
|
||||
}),
|
||||
|
||||
[MEDIA_OVERLAY_ACTIONS.STOP_MEDIA_AUTO_CLOSE]: (state, action) => ({
|
||||
...state,
|
||||
autoClose: {
|
||||
...state.autoClose,
|
||||
active: false,
|
||||
remainingTime: 0,
|
||||
},
|
||||
timestamp: action.payload.timestamp,
|
||||
lastAction: MEDIA_OVERLAY_ACTIONS.STOP_MEDIA_AUTO_CLOSE,
|
||||
}),
|
||||
|
||||
[MEDIA_OVERLAY_ACTIONS.RESET_MEDIA_AUTO_CLOSE]: (state, action) => ({
|
||||
...state,
|
||||
autoClose: {
|
||||
...state.autoClose,
|
||||
remainingTime: state.autoClose.timeout,
|
||||
active: true,
|
||||
},
|
||||
timestamp: action.payload.timestamp,
|
||||
lastAction: MEDIA_OVERLAY_ACTIONS.RESET_MEDIA_AUTO_CLOSE,
|
||||
}),
|
||||
|
||||
[MEDIA_OVERLAY_ACTIONS.UPDATE_MEDIA_AUTO_CLOSE_TIMEOUT]: (state, action) => ({
|
||||
...state,
|
||||
autoClose: {
|
||||
...state.autoClose,
|
||||
timeout: action.payload.timeout,
|
||||
remainingTime: action.payload.timeout,
|
||||
},
|
||||
timestamp: action.payload.timestamp,
|
||||
lastAction: MEDIA_OVERLAY_ACTIONS.UPDATE_MEDIA_AUTO_CLOSE_TIMEOUT,
|
||||
}),
|
||||
|
||||
[MEDIA_OVERLAY_ACTIONS.SET_MEDIA_OVERLAY_VISIBILITY]: (state, action) => ({
|
||||
...state,
|
||||
controls: {
|
||||
...state.controls,
|
||||
...action.payload,
|
||||
},
|
||||
timestamp: action.payload.timestamp,
|
||||
lastAction: MEDIA_OVERLAY_ACTIONS.SET_MEDIA_OVERLAY_VISIBILITY,
|
||||
}),
|
||||
};
|
||||
|
||||
/**
|
||||
* Main Reducer
|
||||
* @param {Object} state - 현재 상태
|
||||
* @param {Object} action - Redux action
|
||||
* @returns {Object} 다음 상태
|
||||
*/
|
||||
export const mediaOverlayReducer = (state = initialState, action = {}) => {
|
||||
const handler = handlers[action.type];
|
||||
|
||||
if (handler) {
|
||||
return handler(state, action);
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
@@ -2,6 +2,7 @@ import { applyMiddleware, combineReducers, createStore } from 'redux';
|
||||
import thunk from 'redux-thunk';
|
||||
|
||||
import { autoCloseMiddleware } from '../middleware/autoCloseMiddleware';
|
||||
import { mediaAutoCloseMiddleware } from '../middleware/mediaAutoCloseMiddleware';
|
||||
import { panelHistoryMiddleware } from '../middleware/panelHistoryMiddleware';
|
||||
import panelQueueMiddleware from '../middleware/panelQueueMiddleware';
|
||||
import { appDataReducer } from '../reducers/appDataReducer';
|
||||
@@ -36,6 +37,7 @@ import { searchReducer } from '../reducers/searchReducer';
|
||||
import { shippingReducer } from '../reducers/shippingReducer';
|
||||
import { toastReducer } from '../reducers/toastReducer';
|
||||
import { videoOverlayReducer } from '../reducers/videoOverlayReducer';
|
||||
import { mediaOverlayReducer } from '../reducers/mediaOverlayReducer';
|
||||
import { videoPlayReducer } from '../reducers/videoPlayReducer';
|
||||
import { voiceReducer } from '../reducers/voiceReducer';
|
||||
|
||||
@@ -69,6 +71,7 @@ const rootReducer = combineReducers({
|
||||
foryou: foryouReducer,
|
||||
toast: toastReducer,
|
||||
videoOverlay: videoOverlayReducer,
|
||||
mediaOverlay: mediaOverlayReducer,
|
||||
videoPlay: videoPlayReducer,
|
||||
voice: voiceReducer,
|
||||
convert: convertReducer,
|
||||
@@ -76,5 +79,5 @@ const rootReducer = combineReducers({
|
||||
|
||||
export const store = createStore(
|
||||
rootReducer,
|
||||
applyMiddleware(thunk, panelHistoryMiddleware, autoCloseMiddleware, panelQueueMiddleware)
|
||||
applyMiddleware(thunk, panelHistoryMiddleware, autoCloseMiddleware, mediaAutoCloseMiddleware, panelQueueMiddleware)
|
||||
);
|
||||
|
||||
@@ -11,13 +11,19 @@ import playImg from '../../../../../assets/images/btn/btn-play-thumb-nor.png';
|
||||
import {
|
||||
switchToModal,
|
||||
switchToFullscreen,
|
||||
showControls,
|
||||
hideControls,
|
||||
toggleControls,
|
||||
startAutoClose,
|
||||
stopAutoClose,
|
||||
resetAutoClose,
|
||||
} from '../../../../actions/videoOverlayActions';
|
||||
import {
|
||||
setMediaControlShow,
|
||||
setMediaControlHide,
|
||||
setMediaControlToggle,
|
||||
startMediaAutoClose,
|
||||
stopMediaAutoClose,
|
||||
resetMediaAutoClose,
|
||||
} from '../../../../actions/mediaOverlayActions';
|
||||
import { pauseFullscreenVideo, resumeFullscreenVideo, clearAllVideoTimers } from '../../../../actions/playActions';
|
||||
import css from './ProductVideo.module.less';
|
||||
|
||||
@@ -60,6 +66,7 @@ export function ProductVideoV2({
|
||||
// Redux 상태 및 dispatch
|
||||
const dispatch = useDispatch();
|
||||
const overlayState = useSelector((state) => state.videoOverlay);
|
||||
const mediaOverlayState = useSelector((state) => state.mediaOverlay);
|
||||
|
||||
// Local state
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
@@ -160,13 +167,12 @@ export function ProductVideoV2({
|
||||
// console.log('[BgVideo] ProductVideoV2 - Pausing background fullscreen video');
|
||||
dispatch(pauseFullscreenVideo());
|
||||
|
||||
// Redux: 오버레이 상태 초기화
|
||||
dispatch(showControls());
|
||||
dispatch(startAutoClose(3000));
|
||||
// MediaPlayer 직접 제어: controls 표시 및 autoClose 시작
|
||||
// Redux: mediaOverlay 상태 초기화 (MediaPlayer 전용)
|
||||
dispatch(setMediaControlShow());
|
||||
dispatch(startMediaAutoClose(3000));
|
||||
// MediaPlayer 직접 제어: controls 표시
|
||||
setTimeout(() => {
|
||||
videoPlayerRef.current?.showControls?.();
|
||||
videoPlayerRef.current?.startAutoCloseTimeout?.();
|
||||
}, 100);
|
||||
}
|
||||
}, [canPlayVideo, isPlaying, dispatch]);
|
||||
@@ -181,11 +187,10 @@ export function ProductVideoV2({
|
||||
// console.log('[BgVideo] ProductVideoV2 - Resuming background fullscreen video');
|
||||
dispatch(resumeFullscreenVideo());
|
||||
|
||||
// Redux: 오버레이 상태 정리
|
||||
dispatch(stopAutoClose());
|
||||
dispatch(hideControls());
|
||||
// MediaPlayer 직접 제어: autoClose 타이머 중지 및 controls 숨김
|
||||
videoPlayerRef.current?.stopAutoCloseTimeout?.();
|
||||
// Redux: mediaOverlay 상태 정리
|
||||
dispatch(stopMediaAutoClose());
|
||||
dispatch(setMediaControlHide());
|
||||
// MediaPlayer 직접 제어: controls 숨김
|
||||
videoPlayerRef.current?.hideControls?.();
|
||||
}, [dispatch]);
|
||||
|
||||
@@ -220,10 +225,9 @@ export function ProductVideoV2({
|
||||
// console.log('[BgVideo] ProductVideoV2 - Resuming background fullscreen video');
|
||||
dispatch(resumeFullscreenVideo());
|
||||
|
||||
dispatch(stopAutoClose());
|
||||
dispatch(hideControls());
|
||||
// MediaPlayer 직접 제어: autoClose 타이머 중지 및 controls 숨김
|
||||
videoPlayerRef.current?.stopAutoCloseTimeout?.();
|
||||
dispatch(stopMediaAutoClose());
|
||||
dispatch(setMediaControlHide());
|
||||
// MediaPlayer 직접 제어: controls 숨김
|
||||
videoPlayerRef.current?.hideControls?.();
|
||||
}
|
||||
}, [isFullscreen, isPlaying, dispatch]);
|
||||
@@ -231,9 +235,8 @@ export function ProductVideoV2({
|
||||
// 사용자 활동 감지 시 autoClose 타이머 리셋
|
||||
const handleUserActivity = useCallback(() => {
|
||||
if (isPlaying && !isFullscreen) {
|
||||
// console.log('[ProductVideoV2] User activity detected - resetting autoClose timer');
|
||||
dispatch(resetAutoClose());
|
||||
videoPlayerRef.current?.startAutoCloseTimeout?.();
|
||||
console.log('🎬 [ProductVideoV2] User activity detected - resetting mediaAutoClose timer');
|
||||
dispatch(resetMediaAutoClose());
|
||||
}
|
||||
}, [isPlaying, isFullscreen, dispatch]);
|
||||
|
||||
@@ -321,8 +324,7 @@ export function ProductVideoV2({
|
||||
|
||||
// FullScreen 모드에서의 MediaPlayer Click 핸들러
|
||||
const handleVideoPlayerClick = useCallback(
|
||||
(e) => {
|
||||
console.log('>>>>>>>>>>>>>>handleVideoPlayerClick');
|
||||
(e) => {
|
||||
if (!isPlaying) return;
|
||||
|
||||
if (!isFullscreen) {
|
||||
@@ -330,54 +332,40 @@ export function ProductVideoV2({
|
||||
e.stopPropagation?.();
|
||||
toggleFullscreen();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('>>>>>>>>>>>>>>handleVideoPlayerClick-1');
|
||||
}
|
||||
// fullscreen: overlay toggle
|
||||
e.preventDefault?.();
|
||||
e.stopPropagation?.();
|
||||
|
||||
console.log('>>>>>>>>>>>>>>handleVideoPlayerClick-2');
|
||||
// Redux overlayState 사용
|
||||
const isCurrentlyVisible = overlayState.controls?.visible;
|
||||
// VideoPlayer API로 실제 상태 확인
|
||||
const isCurrentlyVisible = videoPlayerRef.current?.areControlsVisible?.();
|
||||
if (isCurrentlyVisible) {
|
||||
console.log('>>>>>>>>>>>>>>handleVideoPlayerClick-2-1 isCurrentlyVisible-true', isCurrentlyVisible);
|
||||
dispatch(hideControls());
|
||||
videoPlayerRef.current.hideControls?.();
|
||||
videoPlayerRef.current?.hideControls?.();
|
||||
dispatch(setMediaControlHide()); // MediaOverlay Redux 상태 동기화
|
||||
} else {
|
||||
console.log('>>>>>>>>>>>>>>handleVideoPlayerClick-2-2 isCurrentlyVisible-false', isCurrentlyVisible);
|
||||
dispatch(showControls());
|
||||
videoPlayerRef.current.showControls?.();
|
||||
videoPlayerRef.current.startAutoCloseTimeout?.();
|
||||
videoPlayerRef.current?.showControls?.();
|
||||
dispatch(setMediaControlShow()); // MediaOverlay Redux 상태 동기화
|
||||
}
|
||||
},
|
||||
[isPlaying, isFullscreen, toggleFullscreen, dispatch, overlayState.controls?.visible]
|
||||
[isPlaying, isFullscreen, toggleFullscreen, dispatch, mediaOverlayState.controls?.visible]
|
||||
);
|
||||
|
||||
// 오버레이 토글 통합 함수 (ESC와 동일한 효과)
|
||||
const toggleOverlayVisibility = useCallback(() => {
|
||||
console.log('🖥️ [toggleOverlayVisibility] 오버레이 토글 시작');
|
||||
console.log('🖥️ [toggleOverlayVisibility] 현재 오버레이 상태:', overlayState.controls?.visible);
|
||||
|
||||
const isOverlayVisible = overlayState.controls?.visible;
|
||||
const isOverlayVisible = mediaOverlayState.controls?.visible;
|
||||
if (isOverlayVisible) {
|
||||
console.log('🖥️ [toggleOverlayVisibility] 오버레이 숨기기 (ESC와 동일)');
|
||||
dispatch(hideControls());
|
||||
dispatch(setMediaControlHide());
|
||||
videoPlayerRef.current?.hideControls?.();
|
||||
} else {
|
||||
console.log('🖥️ [toggleOverlayVisibility] 오버레이 보이기');
|
||||
dispatch(showControls());
|
||||
dispatch(setMediaControlShow());
|
||||
videoPlayerRef.current?.showControls?.();
|
||||
videoPlayerRef.current?.startAutoCloseTimeout?.();
|
||||
}
|
||||
}, [overlayState.controls?.visible, dispatch]);
|
||||
}, [mediaOverlayState.controls?.visible, dispatch]);
|
||||
|
||||
// 전체 화면 컨테이너용 마우스 다운 핸들러 (Capture Phase)
|
||||
const handleFullscreenContainerMouseDownCapture = useCallback((e) => {
|
||||
if (!isPlaying || !isFullscreen) return;
|
||||
|
||||
console.log('🖥️ [Fullscreen Container] 마우스 클릭 - 토글 실행');
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// toggleOverlayVisibility();
|
||||
@@ -512,8 +500,8 @@ export function ProductVideoV2({
|
||||
dispatch(resumeFullscreenVideo());
|
||||
}
|
||||
|
||||
dispatch(stopAutoClose());
|
||||
dispatch(hideControls());
|
||||
dispatch(stopMediaAutoClose());
|
||||
dispatch(setMediaControlHide());
|
||||
};
|
||||
}, [dispatch, isPlaying]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user