diff --git a/com.twin.app.shoptime/src/actions/mediaActions.js b/com.twin.app.shoptime/src/actions/mediaActions.js index 32d5ca84..abe44e77 100644 --- a/com.twin.app.shoptime/src/actions/mediaActions.js +++ b/com.twin.app.shoptime/src/actions/mediaActions.js @@ -90,18 +90,18 @@ export const finishMediaPreview = () => (dispatch, getState) => { }; /** - * 강제로 modal MediaPanel을 종료합니다 (스택 어디에 있든) + * 강제로 DetailPanel ProductVideo MediaPanel을 종료합니다 (modal/fullscreen 모두) */ export const finishModalMediaForce = () => (dispatch, getState) => { const panels = getState().panels.panels; - const hasModalMediaPanel = panels.some( - (panel) => panel.name === panel_names.MEDIA_PANEL && panel.panelInfo?.modal + const hasProductVideoPanel = panels.some( + (panel) => + panel.name === panel_names.MEDIA_PANEL && + (panel.panelInfo?.modal || panel.panelInfo?.modalContainerId === 'product-video-player') ); - if (hasModalMediaPanel) { - // console.log('[finishModalMediaForce] Force closing modal MediaPanel'); - + if (hasProductVideoPanel) { if (startMediaFocusTimer) { clearTimeout(startMediaFocusTimer); startMediaFocusTimer = null; diff --git a/com.twin.app.shoptime/src/actions/playActions.js b/com.twin.app.shoptime/src/actions/playActions.js index 12c64e23..463074a6 100644 --- a/com.twin.app.shoptime/src/actions/playActions.js +++ b/com.twin.app.shoptime/src/actions/playActions.js @@ -326,10 +326,10 @@ export const shrinkVideoTo1px = () => (dispatch, getState) => { }, }; - console.log('[HomePanel] shrinkVideoTo1px: saving shrinkInfo', { - shrinkInfo: updatedPlayerState.shrinkInfo, - modalStyle: panelInfo.modalStyle, - }); + // console.log('[HomePanel] shrinkVideoTo1px: saving shrinkInfo', { + // shrinkInfo: updatedPlayerState.shrinkInfo, + // modalStyle: panelInfo.modalStyle, + // }); dispatch( updatePanel({ @@ -359,11 +359,11 @@ export const expandVideoFrom1px = () => (dispatch, getState) => { const panelInfo = shrunkModalPlayerPanel.panelInfo; const shrinkInfo = panelInfo.playerState?.shrinkInfo; - console.log('[HomePanel] expandVideoFrom1px: expanding video', { - hasShrinkInfo: !!shrinkInfo, - hasModalStyle: !!shrinkInfo?.modalStyle, - hasModalContainerId: !!shrinkInfo?.modalContainerId, - }); + // console.log('[HomePanel] expandVideoFrom1px: expanding video', { + // hasShrinkInfo: !!shrinkInfo, + // hasModalStyle: !!shrinkInfo?.modalStyle, + // hasModalContainerId: !!shrinkInfo?.modalContainerId, + // }); const updatedPanelInfo = { ...panelInfo, @@ -378,7 +378,7 @@ export const expandVideoFrom1px = () => (dispatch, getState) => { }), }; - console.log('[HomePanel] expandVideoFrom1px: updated panelInfo shouldShrinkTo1px=false, modalStyle restored, skipModalStyleRecalculation=true'); + // console.log('[HomePanel] expandVideoFrom1px: updated panelInfo shouldShrinkTo1px=false, modalStyle restored, skipModalStyleRecalculation=true'); dispatch( updatePanel({ diff --git a/com.twin.app.shoptime/src/components/VideoPlayer/MediaPlayer.v2.jsx b/com.twin.app.shoptime/src/components/VideoPlayer/MediaPlayer.v2.jsx index eac53ba0..7480d61e 100644 --- a/com.twin.app.shoptime/src/components/VideoPlayer/MediaPlayer.v2.jsx +++ b/com.twin.app.shoptime/src/components/VideoPlayer/MediaPlayer.v2.jsx @@ -112,6 +112,7 @@ const MediaPlayerV2 = forwardRef((props, ref) => { onLoadedData, onLoadedMetadata, onDurationChange, + setApiProvider, // Spotlight spotlightId = 'mediaPlayerV2', @@ -470,6 +471,23 @@ const MediaPlayerV2 = forwardRef((props, ref) => { getVideoNode: () => videoRef.current, }), [play, pause, seek, getMediaState, showControls, hideControls, toggleControls, controlsVisible]); + // ========== setApiProvider 호출 ========== + useEffect(() => { + if (setApiProvider && typeof setApiProvider === 'function') { + setApiProvider({ + play, + pause, + seek, + getMediaState, + showControls, + hideControls, + toggleControls, + areControlsVisible: () => controlsVisible, + getVideoNode: () => videoRef.current, + }); + } + }, [setApiProvider, play, pause, seek, getMediaState, showControls, hideControls, toggleControls]); + // ========== Video Props ========== const videoProps = useMemo(() => { const baseProps = { @@ -671,11 +689,13 @@ MediaPlayerV2.propTypes = { style: PropTypes.object, modalClassName: PropTypes.string, modalScale: PropTypes.number, + setApiProvider: PropTypes.func, // 패널 정보 panelInfo: PropTypes.shape({ modal: PropTypes.bool, modalContainerId: PropTypes.string, + modalClassName: PropTypes.string, isPaused: PropTypes.bool, showUrl: PropTypes.string, thumbnailUrl: PropTypes.string, diff --git a/com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx b/com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx index 3909f40b..42bd6c2a 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx @@ -22,6 +22,7 @@ import { getMainCategoryDetail, getMainYouMayLike, } from '../../actions/mainActions'; +import { finishModalMediaForce } from '../../actions/mediaActions'; import { popPanel, updatePanel, @@ -141,11 +142,19 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { Spotlight.focus('spotlightId_backBtn'); }, []); + useEffect(() => { + return () => { + dispatch(finishModalMediaForce()); + }; + }, [dispatch]); + const onClick = useCallback( (isCancelClick) => (ev) => { fp.pipe( () => { dispatch(clearAllToasts()); // BuyOption Toast 포함 모든 토스트 제거 + dispatch(pauseFullscreenVideo()); // PLAYER_PANEL 비디오 중지 + dispatch(finishModalMediaForce()); // MEDIA_PANEL(ProductVideo) 강제 종료 dispatch(finishVideoPreview()); dispatch(popPanel(panel_names.DETAIL_PANEL)); }, diff --git a/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx b/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx index 63ca9a53..ca4e8058 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx @@ -27,7 +27,7 @@ import arrowDown import indicatorDefaultImage from '../../../../assets/images/img-thumb-empty-144@3x.png'; // import { pushPanel } from '../../../actions/panelActions'; -import { minimizeModalMedia } from '../../../actions/mediaActions'; +import { minimizeModalMedia, restoreModalMedia } from '../../../actions/mediaActions'; import { pauseFullscreenVideo } from '../../../actions/playActions'; import { resetShowAllReviews } from '../../../actions/productActions'; import { @@ -197,6 +197,9 @@ export default function ProductAllSection({ // ProductAllSection 초기 로딩 시 Skeleton 표시를 위한 상태 const [isInitialLoading, setIsInitialLoading] = useState(true); + // 스크롤 위치에 따른 MediaPanel 제어 상태 + const [shouldMinimizeMedia, setShouldMinimizeMedia] = useState(false); + useEffect(() => { const toggleQRCode = () => { if (isShowQRCode) { @@ -498,7 +501,7 @@ export default function ProductAllSection({ // ProductVideo V1 전용 - MediaPanel minimize 포함 const handleScrollToImagesV1 = useCallback(() => { // 1. MediaPanel을 1px로 축소하여 포커스 충돌 방지 - dispatch(minimizeModalMedia()); + // dispatch(minimizeModalMedia()); // 2. 스크롤 이동 scrollToSection('scroll-marker-after-video'); @@ -657,6 +660,7 @@ export default function ProductAllSection({ [scrollToSection] ); const scrollPositionRef = useRef(0); + const prevScrollPositionRef = useRef(0); // 이전 스크롤 위치 추적 const handleArrowClickAlternative = useCallback(() => { const currentHeight = scrollPositionRef.current; @@ -677,17 +681,40 @@ export default function ProductAllSection({ } }, [documentHeight, scrollTop]); - const handleScroll = useCallback( + // const handleScroll = useCallback( + // (e) => { + // scrollPositionRef.current = e.scrollTop; + // if (documentHeight) { + // const isAtBottom = + // scrollPositionRef.current + 944 >= + // documentHeight + (youMayAlsoLikelRef.current?.scrollHeight || 0); + // if (isAtBottom !== isBottom) { + // setIsBottom(isAtBottom); + // } + // } + // }, + // [documentHeight, isBottom] + // ); + + // 스크롤 멈추었을 때만 호출 (성능 최적화) + const handleScrollStop = useCallback( (e) => { - scrollPositionRef.current = e.scrollTop; + const currentScrollTop = e.scrollTop; + scrollPositionRef.current = currentScrollTop; + if (documentHeight) { const isAtBottom = - scrollPositionRef.current + 944 >= + currentScrollTop + 944 >= documentHeight + (youMayAlsoLikelRef.current?.scrollHeight || 0); if (isAtBottom !== isBottom) { setIsBottom(isAtBottom); } } + + console.log('📍 [ProductAllSection] 스크롤 멈춤 - 위치:', currentScrollTop); + const shouldMinimize = currentScrollTop > 0; + console.log('📍 [ProductAllSection] setShouldMinimizeMedia 호출:', shouldMinimize); + setShouldMinimizeMedia(shouldMinimize); // state 업데이트만 }, [documentHeight, isBottom] ); @@ -710,6 +737,18 @@ export default function ProductAllSection({ ); }, [hasReviews, hasYouMayAlsoLike]); + // 스크롤 위치에 따른 MediaPanel 제어 (비디오 재생 중에는 자동 제어 안함 - unmount 시에만 정리) + // useEffect(() => { + // console.log('📍 [ProductAllSection] useEffect 실행 - shouldMinimizeMedia:', shouldMinimizeMedia); + // if (shouldMinimizeMedia) { + // console.log('📍 [ProductAllSection] MediaPanel minimize 호출 (true)'); + // dispatch(minimizeModalMedia()); + // } else { + // console.log('📍 [ProductAllSection] MediaPanel restore 호출 (false)'); + // dispatch(restoreModalMedia()); + // } + // }, [shouldMinimizeMedia, dispatch]); + //spot관련 useEffect(() => { if (onReady && isOnRender) { @@ -768,9 +807,11 @@ export default function ProductAllSection({ // }; // }, []); - // 컴포넌트 unmount 시 모든 timer cleanup + // 컴포넌트 unmount 시 timer cleanup useEffect(() => { return () => { + console.log('[ProductAllSection] unmount - cleanup 시작'); + // QR code timer cleanup if (timerRef.current) { clearTimeout(timerRef.current); @@ -788,7 +829,7 @@ export default function ProductAllSection({ scrollToImagesTimeoutRef.current = null; } - // console.log('[ProductAllSection] All timers cleaned up on unmount'); + console.log('[ProductAllSection] cleanup 완료 on unmount'); }; }, []); @@ -1019,7 +1060,8 @@ export default function ProductAllSection({ spotlightId="main-content-scroller" spotlightDisabled={false} spotlightRestrict="none" - onScroll={handleScroll} + // onScroll={handleScroll} + onScrollStop={handleScrollStop} onClick={handleScrollContainerClick} >
diff --git a/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.jsx b/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.jsx index 4644d06b..d6609d7a 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.jsx @@ -114,17 +114,17 @@ export default function ProductVideo({ setFocused(true); console.log('[ProductVideo] Calling restoreModalMedia'); // ProductVideo에 포커스가 돌아오면 비디오 복원 - dispatch(restoreModalMedia()); + // dispatch(restoreModalMedia()); } }, [canPlayVideo, dispatch]); const videoContainerOnBlur = useCallback(() => { console.log('[ProductVideo] onBlur called - canPlayVideo:', canPlayVideo); - if (canPlayVideo) { - setFocused(false); - // 포커스를 잃으면 모달 MediaPanel을 최소화하여 1px 상태로 유지 - dispatch(minimizeModalMedia()); - } + // if (canPlayVideo) { + // setFocused(false); + // // 포커스를 잃으면 모달 MediaPanel을 최소화하여 1px 상태로 유지 + // dispatch(minimizeModalMedia()); + // } }, [canPlayVideo, dispatch]); // Spotlight Down 키 핸들러 - 비디오 다음 이미지로 스크롤 diff --git a/com.twin.app.shoptime/src/views/HomePanel/HomePanel.jsx b/com.twin.app.shoptime/src/views/HomePanel/HomePanel.jsx index 123fdc68..a2268e33 100644 --- a/com.twin.app.shoptime/src/views/HomePanel/HomePanel.jsx +++ b/com.twin.app.shoptime/src/views/HomePanel/HomePanel.jsx @@ -466,7 +466,7 @@ const HomePanel = ({ isOnTop }) => { // shouldShrinkTo1px가 false가 되면 interval 즉시 정리 if (!playerPanelShouldShrink && expandIntervalRef.current) { - console.log('[HomePanel] shouldShrinkTo1px=false - clearing expand interval'); + // console.log('[HomePanel] shouldShrinkTo1px=false - clearing expand interval'); clearInterval(expandIntervalRef.current); expandIntervalRef.current = null; expandAttemptRef.current = 0; @@ -489,20 +489,20 @@ const HomePanel = ({ isOnTop }) => { // ✅ 최상단 도달: 1px 비디오 복구 시도 (shouldShrinkRef.current로 읽음) if (currentScrollTop <= 1) { if (shouldShrinkRef.current && !expandIntervalRef.current) { - console.log('[HomePanel] At top (scrollTop <= 1) - starting video expansion'); + // console.log('[HomePanel] At top (scrollTop <= 1) - starting video expansion'); expandAttemptRef.current = 0; // Interval 시작: 200ms마다 복구 시도 expandIntervalRef.current = setInterval(() => { // 종료 조건: 최대 3회 시도 if (expandAttemptRef.current >= 3) { - console.log('[HomePanel] Max expansion attempts reached (3)'); + // console.log('[HomePanel] Max expansion attempts reached (3)'); clearInterval(expandIntervalRef.current); expandIntervalRef.current = null; return; } - console.log('[HomePanel] Expansion attempt', expandAttemptRef.current + 1); + // console.log('[HomePanel] Expansion attempt', expandAttemptRef.current + 1); dispatch(expandVideoFrom1px()); expandAttemptRef.current++; }, 200); @@ -510,7 +510,7 @@ const HomePanel = ({ isOnTop }) => { } // 최상단 벗어남: interval 정리 else if (currentScrollTop > 1 && expandIntervalRef.current) { - console.log('[HomePanel] Left top - clearing expand interval'); + // console.log('[HomePanel] Left top - clearing expand interval'); clearInterval(expandIntervalRef.current); expandIntervalRef.current = null; } @@ -518,7 +518,7 @@ const HomePanel = ({ isOnTop }) => { // 기존 로직: 아래로 스크롤 if (currentScrollTop > prevScrollTop) { // 아래로 스크롤: 비디오를 1px로 축소 - console.log('[HomePanel] Scrolling down - shrinking video'); + // console.log('[HomePanel] Scrolling down - shrinking video'); dispatch(shrinkVideoTo1px()); // 기존 타이머 취소 if (scrollExpandTimerRef.current) { @@ -529,14 +529,14 @@ const HomePanel = ({ isOnTop }) => { // 기존 로직: 위로 스크롤 (0이 아닌) else if (currentScrollTop < prevScrollTop && currentScrollTop > 1) { // 위로 스크롤 (최상단 아님): 1초 후 복구 - console.log('[HomePanel] Scrolling up - will expand after 1s'); + // console.log('[HomePanel] Scrolling up - will expand after 1s'); // 기존 타이머 취소 if (scrollExpandTimerRef.current) { clearTimeout(scrollExpandTimerRef.current); } // 1초 후 자동으로 크기 조정 scrollExpandTimerRef.current = setTimeout(() => { - console.log('[HomePanel] 1s passed - auto expanding video'); + // console.log('[HomePanel] 1s passed - auto expanding video'); dispatch(expandVideoFrom1px()); scrollExpandTimerRef.current = null; }, 1000); diff --git a/com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.jsx b/com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.jsx index a6cf7421..17ce6b55 100644 --- a/com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.jsx +++ b/com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.jsx @@ -507,7 +507,7 @@ const MediaPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props loop={panelInfo.continuousPlay || false} style={panelInfo.modal ? modalStyle : {}} modalScale={panelInfo.modal ? modalScale : 1} - modalClassName={panelInfo.modal && panelInfo.modalClassName} + modalClassName={panelInfo.modal ? panelInfo.modalClassName : undefined} onError={mediainfoHandler} onTimeUpdate={mediainfoHandler} onLoadedData={mediainfoHandler}