diff --git a/com.twin.app.shoptime/src/actions/playActions.js b/com.twin.app.shoptime/src/actions/playActions.js index 24d20d06..a0a02060 100644 --- a/com.twin.app.shoptime/src/actions/playActions.js +++ b/com.twin.app.shoptime/src/actions/playActions.js @@ -139,13 +139,20 @@ export const resumeModalVideo = () => (dispatch, getState) => { export const pauseFullscreenVideo = () => (dispatch, getState) => { const panels = getState().panels.panels; + // console.log('[BgVideo] pauseFullscreenVideo called - panels:', { + // panelsCount: panels?.length, + // panels: panels?.map(p => ({ name: p.name, modal: p.panelInfo?.modal, isPaused: p.panelInfo?.isPaused })) + // }); + // 전체화면 PlayerPanel 찾기 (modal이 false인 패널) const fullscreenPlayerPanel = panels.find( (panel) => panel.name === panel_names.PLAYER_PANEL && !panel.panelInfo?.modal ); + // console.log('[BgVideo] pauseFullscreenVideo - fullscreenPlayerPanel found:', !!fullscreenPlayerPanel); + if (fullscreenPlayerPanel) { - console.log('[pauseFullscreenVideo] Pausing fullscreen video'); + // console.log('[BgVideo] pauseFullscreenVideo - dispatching updatePanel with isPaused: true'); dispatch( updatePanel({ name: panel_names.PLAYER_PANEL, @@ -155,6 +162,8 @@ export const pauseFullscreenVideo = () => (dispatch, getState) => { }, }) ); + } else { + console.log('[BgVideo] pauseFullscreenVideo - No fullscreen PlayerPanel found, skipping'); } }; @@ -162,13 +171,21 @@ export const pauseFullscreenVideo = () => (dispatch, getState) => { export const resumeFullscreenVideo = () => (dispatch, getState) => { const panels = getState().panels.panels; + // console.log('[BgVideo] resumeFullscreenVideo called - panels:', { + // panelsCount: panels?.length, + // panels: panels?.map(p => ({ name: p.name, modal: p.panelInfo?.modal, isPaused: p.panelInfo?.isPaused })) + // }); + // 전체화면 PlayerPanel 찾기 (modal이 false인 패널) const fullscreenPlayerPanel = panels.find( (panel) => panel.name === panel_names.PLAYER_PANEL && !panel.panelInfo?.modal ); + // console.log('[BgVideo] resumeFullscreenVideo - fullscreenPlayerPanel found:', !!fullscreenPlayerPanel); + // console.log('[BgVideo] resumeFullscreenVideo - isPaused:', fullscreenPlayerPanel?.panelInfo?.isPaused); + if (fullscreenPlayerPanel && fullscreenPlayerPanel.panelInfo?.isPaused) { - console.log('[resumeFullscreenVideo] Resuming fullscreen video'); + // console.log('[BgVideo] resumeFullscreenVideo - dispatching updatePanel with isPaused: false'); dispatch( updatePanel({ name: panel_names.PLAYER_PANEL, @@ -178,6 +195,8 @@ export const resumeFullscreenVideo = () => (dispatch, getState) => { }, }) ); + } else { + console.log('[BgVideo] resumeFullscreenVideo - Not resuming (not found or not paused)'); } }; diff --git a/com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx b/com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx index d9b1e470..68235ba2 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx @@ -1,116 +1,89 @@ // src/views/DetailPanel/DetailPanel.new.jsx -import React, { - useCallback, - useEffect, - useLayoutEffect, - useMemo, - useRef, - useState, -} from "react"; +import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; -import { useDispatch, useSelector } from "react-redux"; +import { useDispatch, useSelector } from 'react-redux'; -import Spinner from "@enact/sandstone/Spinner"; -import Spotlight from "@enact/spotlight"; -import { setContainerLastFocusedElement } from "@enact/spotlight/src/container"; +import Spinner from '@enact/sandstone/Spinner'; +import Spotlight from '@enact/spotlight'; +import { setContainerLastFocusedElement } from '@enact/spotlight/src/container'; -import indicatorDefaultImage from "../../../assets/images/img-thumb-empty-144@3x.png"; -import { getDeviceAdditionInfo } from "../../actions/deviceActions"; -import { getThemeCurationDetailInfo } from "../../actions/homeActions"; +import indicatorDefaultImage from '../../../assets/images/img-thumb-empty-144@3x.png'; +import { getDeviceAdditionInfo } from '../../actions/deviceActions'; +import { getThemeCurationDetailInfo } from '../../actions/homeActions'; +import { getMainCategoryDetail, getMainYouMayLike } from '../../actions/mainActions'; +import { popPanel, updatePanel } from '../../actions/panelActions'; import { - getMainCategoryDetail, - getMainYouMayLike, -} from "../../actions/mainActions"; -import { popPanel, updatePanel } from "../../actions/panelActions"; -import { finishVideoPreview } from "../../actions/playActions"; -import { - clearProductDetail, - getProductOptionId, -} from "../../actions/productActions"; -import TBody from "../../components/TBody/TBody"; -import TPanel from "../../components/TPanel/TPanel"; -import { panel_names } from "../../utils/Config"; -import fp from "../../utils/fp"; -import { $L, getQRCodeUrl } from "../../utils/helperMethods"; -import { SpotlightIds } from "../../utils/SpotlightIds"; -import DetailPanelBackground from "./components/DetailPanelBackground"; -import THeaderCustom from "./components/THeaderCustom"; -import css from "./DetailPanel.module.less"; -import DetailPanelSkeleton from "./DetailPanelSkeleton/DetailPanelSkeleton"; -import ProductAllSection from "./ProductAllSection/ProductAllSection"; -import ThemeItemListOverlay from "./ThemeItemListOverlay/ThemeItemListOverlay"; + finishVideoPreview, + pauseFullscreenVideo, + resumeFullscreenVideo, +} from '../../actions/playActions'; +import { clearProductDetail, getProductOptionId } from '../../actions/productActions'; +import TBody from '../../components/TBody/TBody'; +import TPanel from '../../components/TPanel/TPanel'; +import { panel_names } from '../../utils/Config'; +import fp from '../../utils/fp'; +import { $L, getQRCodeUrl } from '../../utils/helperMethods'; +import { SpotlightIds } from '../../utils/SpotlightIds'; +import DetailPanelBackground from './components/DetailPanelBackground'; +import THeaderCustom from './components/THeaderCustom'; +import css from './DetailPanel.module.less'; +import DetailPanelSkeleton from './DetailPanelSkeleton/DetailPanelSkeleton'; +import ProductAllSection from './ProductAllSection/ProductAllSection'; +import ThemeItemListOverlay from './ThemeItemListOverlay/ThemeItemListOverlay'; export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { const dispatch = useDispatch(); const productData = useSelector((state) => state.main.productData); const youmaylikeData = useSelector((state) => state.main.youmaylikeData); - const themeProductInfos = useSelector( - (state) => state.home.themeCurationDetailInfoData - ); + const themeProductInfos = useSelector((state) => state.home.themeCurationDetailInfoData); const isLoading = useSelector((state) => - fp.pipe(() => state, fp.get("common.appStatus.showLoadingPanel.show"))() + fp.pipe(() => state, fp.get('common.appStatus.showLoadingPanel.show'))() ); const themeData = useSelector((state) => fp.pipe( () => state, - fp.get("home.productData.themeInfo"), + fp.get('home.productData.themeInfo'), (list) => list && list[0] )() ); - const webOSVersion = useSelector( - (state) => state.common.appStatus.webOSVersion - ); + const webOSVersion = useSelector((state) => state.common.appStatus.webOSVersion); const panels = useSelector((state) => state.panels.panels); const [selectedIndex, setSelectedIndex] = useState(0); const localRecentItems = useSelector((state) => - fp.pipe(() => state, fp.get("localSettings.recentItems"))() + fp.pipe(() => state, fp.get('localSettings.recentItems'))() ); const { httpHeader } = useSelector((state) => state.common); - const { popupVisible, activePopup } = useSelector( - (state) => state.common.popup - ); - const [lgCatCd, setLgCatCd] = useState(""); + const { popupVisible, activePopup } = useSelector((state) => state.common.popup); + const [lgCatCd, setLgCatCd] = useState(''); const [themeProductInfo, setThemeProductInfo] = useState(null); const containerRef = useRef(null); - const panelType = useMemo( - () => fp.pipe(() => panelInfo, fp.get("type"))(), - [panelInfo] - ); + const panelType = useMemo(() => fp.pipe(() => panelInfo, fp.get('type'))(), [panelInfo]); const panelCurationId = useMemo( - () => fp.pipe(() => panelInfo, fp.get("curationId"))(), - [panelInfo] - ); - const panelPatnrId = useMemo( - () => fp.pipe(() => panelInfo, fp.get("patnrId"))(), - [panelInfo] - ); - const panelPrdtId = useMemo( - () => fp.pipe(() => panelInfo, fp.get("prdtId"))(), + () => fp.pipe(() => panelInfo, fp.get('curationId'))(), [panelInfo] ); + const panelPatnrId = useMemo(() => fp.pipe(() => panelInfo, fp.get('patnrId'))(), [panelInfo]); + const panelPrdtId = useMemo(() => fp.pipe(() => panelInfo, fp.get('prdtId'))(), [panelInfo]); const panelLiveReqFlag = useMemo( - () => fp.pipe(() => panelInfo, fp.get("liveReqFlag"))(), - [panelInfo] - ); - const panelBgImgNo = useMemo( - () => fp.pipe(() => panelInfo, fp.get("bgImgNo"))(), + () => fp.pipe(() => panelInfo, fp.get('liveReqFlag'))(), [panelInfo] ); + const panelBgImgNo = useMemo(() => fp.pipe(() => panelInfo, fp.get('bgImgNo'))(), [panelInfo]); // PlayerPanel에서 진입했는지 여부를 panelInfo에서 추출 const panelLaunchedFromPlayer = useMemo( - () => fp.pipe(() => panelInfo, fp.get("launchedFromPlayer"))(), + () => fp.pipe(() => panelInfo, fp.get('launchedFromPlayer'))(), [panelInfo] ); const productPmtSuptYn = useMemo( - () => fp.pipe(() => productData, fp.get("pmtSuptYn"))(), + () => fp.pipe(() => productData, fp.get('pmtSuptYn'))(), [productData] ); const productGrPrdtProcYn = useMemo( - () => fp.pipe(() => productData, fp.get("grPrdtProcYn"))(), + () => fp.pipe(() => productData, fp.get('grPrdtProcYn'))(), [productData] ); @@ -118,7 +91,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { () => fp.pipe( () => panelType, - (type) => (type === "theme" ? themeData : productData) + (type) => (type === 'theme' ? themeData : productData) )(), [panelType, themeData, productData] ); @@ -144,7 +117,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { const onSpotlightUpTButton = useCallback((e) => { e.stopPropagation(); - Spotlight.focus("spotlightId_backBtn"); + Spotlight.focus('spotlightId_backBtn'); }, []); const onClick = useCallback( @@ -159,12 +132,12 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { const shouldUpdatePanel = fp.pipe( () => panels, - fp.get("length"), + fp.get('length'), (length) => length === 4 )() && fp.pipe( () => panels, - fp.get("1.name"), + fp.get('1.name'), (name) => name === panel_names.PLAYER_PANEL )(); @@ -173,7 +146,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { updatePanel({ name: panel_names.PLAYER_PANEL, panelInfo: { - thumbnail: fp.pipe(() => panelInfo, fp.get("thumbnailUrl"))(), + thumbnail: fp.pipe(() => panelInfo, fp.get('thumbnailUrl'))(), }, }) ); @@ -191,31 +164,29 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { const handleScrollToSection = useCallback( (sectionId) => { - console.log("DetailPanel: handleScrollToSection called with:", sectionId); - console.log("DetailPanel: scrollToSection function:", scrollToSection); + console.log('DetailPanel: handleScrollToSection called with:', sectionId); + console.log('DetailPanel: scrollToSection function:', scrollToSection); const scrollAction = fp.pipe( () => ({ scrollToSection, sectionId }), ({ scrollToSection, sectionId }) => { if (fp.isNotNil(scrollToSection)) { return { - action: "execute", + action: 'execute', scrollFunction: scrollToSection, sectionId, }; } else { - return { action: "store", sectionId }; + return { action: 'store', sectionId }; } } )(); // 액션에 따른 처리 - if (scrollAction.action === "execute") { + if (scrollAction.action === 'execute') { scrollAction.scrollFunction(scrollAction.sectionId); } else { - console.log( - "DetailPanel: scrollToSection function is null, storing pending scroll" - ); + console.log('DetailPanel: scrollToSection function is null, storing pending scroll'); setPendingScrollSection(scrollAction.sectionId); } }, @@ -233,10 +204,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { )(); if (shouldExecutePendingScroll) { - console.log( - "DetailPanel: executing pending scroll to:", - pendingScrollSection - ); + console.log('DetailPanel: executing pending scroll to:', pendingScrollSection); // 메모리 누수 방지를 위한 cleanup 함수 const timeoutId = setTimeout(() => { @@ -262,7 +230,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { }, () => { // 테마 데이터 로딩 - const isThemeType = panelType === "theme"; + const isThemeType = panelType === 'theme'; if (isThemeType) { dispatch( @@ -284,7 +252,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { getMainCategoryDetail({ patnrId: panelPatnrId, prdtId: panelPrdtId, - liveReqFlag: panelLiveReqFlag || "N", + liveReqFlag: panelLiveReqFlag || 'N', }) ); } @@ -315,11 +283,11 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { exclPatnrId: panelInfo?.patnrId, exclPrdtId: panelInfo?.prdtId, catDpTh3: - panelInfo?.type === "theme" + panelInfo?.type === 'theme' ? themeProductInfos[selectedIndex]?.catDpTh3 : productData?.catDpTh3, catDpTh4: - panelInfo?.type === "theme" + panelInfo?.type === 'theme' ? themeProductInfos[selectedIndex]?.catDpTh4 : productData?.catDpTh4, }; @@ -336,7 +304,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { setLgCatCd(productData.catCd); } else if ( themeProductInfos && - themeProductInfos[selectedIndex]?.pmtSuptYn === "N" && + themeProductInfos[selectedIndex]?.pmtSuptYn === 'N' && panelInfo?.curationId ) { const themeCatCd = themeProductInfos[selectedIndex]?.catCd; @@ -350,7 +318,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { // selectedIndex, // themeProductPmtSuptYn: themeProductInfos?.[selectedIndex]?.pmtSuptYn // }); - setLgCatCd(""); + setLgCatCd(''); } }, [productData, themeProductInfos, selectedIndex, panelInfo?.curationId]); @@ -387,7 +355,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { dispatch(clearProductDetail()); }, () => { - setContainerLastFocusedElement(null, ["indicator-GridListContainer"]); + setContainerLastFocusedElement(null, ['indicator-GridListContainer']); } )(); }; @@ -410,9 +378,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { const conditionCheckers = useMemo( () => ({ - hasDataAndCondition: fp.curry( - (conditionFn, data) => fp.isNotNil(data) && conditionFn(data) - ), + hasDataAndCondition: fp.curry((conditionFn, data) => fp.isNotNil(data) && conditionFn(data)), equalTo: fp.curry((expected, actual) => actual === expected), checkAllConditions: fp.curry((conditions, data) => fp.reduce( @@ -443,7 +409,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { // 테마 타입 체크 () => createTypeChecker( - "theme", + 'theme', () => fp.pipe( () => ({ panelCurationId, themeData }), @@ -453,10 +419,10 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { () => { const themeProduct = fp.pipe( () => themeData, - fp.get("productInfos"), + fp.get('productInfos'), fp.get(selectedIndex.toString()) )(); - setProductType("theme"); + setProductType('theme'); setThemeProductInfo(themeProduct); } ), @@ -464,7 +430,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { // Buy Now 타입 체크 (curry 활용) () => createTypeChecker( - "buyNow", + 'buyNow', () => fp.pipe( () => ({ @@ -483,21 +449,21 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { }) => { const conditions = [ () => fp.isNotNil(productData), - () => conditionCheckers.equalTo("Y")(productPmtSuptYn), - () => conditionCheckers.equalTo("N")(productGrPrdtProcYn), + () => conditionCheckers.equalTo('Y')(productPmtSuptYn), + () => conditionCheckers.equalTo('N')(productGrPrdtProcYn), () => fp.isNotNil(panelPrdtId), - () => versionComparators.isVersionGTE("6.0")(webOSVersion), + () => versionComparators.isVersionGTE('6.0')(webOSVersion), ]; return conditionCheckers.checkAllConditions(conditions)({}); } )(), - () => setProductType("buyNow") + () => setProductType('buyNow') ), // Shop By Mobile 타입 체크 (curry 활용) () => createTypeChecker( - "shopByMobile", + 'shopByMobile', () => fp.pipe( () => ({ @@ -516,23 +482,21 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { }) => { if (!productData) return false; - const isDirectMobile = - conditionCheckers.equalTo("N")(productPmtSuptYn); + const isDirectMobile = conditionCheckers.equalTo('N')(productPmtSuptYn); const conditionalMobileConditions = [ - () => conditionCheckers.equalTo("Y")(productPmtSuptYn), - () => conditionCheckers.equalTo("N")(productGrPrdtProcYn), - () => versionComparators.isVersionLT("6.0")(webOSVersion), + () => conditionCheckers.equalTo('Y')(productPmtSuptYn), + () => conditionCheckers.equalTo('N')(productGrPrdtProcYn), + () => versionComparators.isVersionLT('6.0')(webOSVersion), () => fp.isNotNil(panelPrdtId), ]; - const isConditionalMobile = - conditionCheckers.checkAllConditions( - conditionalMobileConditions - )({}); + const isConditionalMobile = conditionCheckers.checkAllConditions( + conditionalMobileConditions + )({}); return isDirectMobile || isConditionalMobile; } )(), - () => setProductType("shopByMobile") + () => setProductType('shopByMobile') ), ]; @@ -552,13 +516,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { productGrPrdtProcYn, webOSVersion, }), - ({ - productData, - panelPrdtId, - productPmtSuptYn, - productGrPrdtProcYn, - webOSVersion, - }) => ({ + ({ productData, panelPrdtId, productPmtSuptYn, productGrPrdtProcYn, webOSVersion }) => ({ pmtSuptYn: productPmtSuptYn, grPrdtProcYn: productGrPrdtProcYn, prdtId: panelPrdtId, @@ -566,8 +524,8 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { }) )(); - console.warn("Unknown product type:", productData); - console.warn("Product data properties:", debugInfo); + console.warn('Unknown product type:', productData); + console.warn('Product data properties:', debugInfo); } }, [ panelCurationId, @@ -583,7 +541,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { useEffect(() => { // productData가 로드된 후에만 getProductType 실행 - if (productData || (panelType === "theme" && themeData)) { + if (productData || (panelType === 'theme' && themeData)) { getProductType(); } }, [getProductType, productData, themeData, panelType]); @@ -597,7 +555,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { }, [themeData, selectedIndex]); const imageUrl = useMemo( - () => fp.pipe(() => productData, fp.get("thumbnailUrl960"))(), + () => fp.pipe(() => productData, fp.get('thumbnailUrl960'))(), [productData] ); @@ -611,21 +569,20 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { () => ({ panelPrdtId, productData }), ({ panelPrdtId, productData }) => fp.isNotNil(panelPrdtId) && - fp.pipe(() => productData, fp.get("prdtNm"), fp.isNotNil)() - ? fp.pipe(() => productData, fp.get("prdtNm"))() + fp.pipe(() => productData, fp.get('prdtNm'), fp.isNotNil)() + ? fp.pipe(() => productData, fp.get('prdtNm'))() : null )(); const themeTitle = fp.pipe( () => ({ panelType, themeData }), ({ panelType, themeData }) => - panelType === "theme" && - fp.pipe(() => themeData, fp.get("curationNm"), fp.isNotNil)() - ? fp.pipe(() => themeData, fp.get("curationNm"))() + panelType === 'theme' && fp.pipe(() => themeData, fp.get('curationNm'), fp.isNotNil)() + ? fp.pipe(() => themeData, fp.get('curationNm'))() : null )(); - return productTitle || themeTitle || ""; + return productTitle || themeTitle || ''; } )(), [panelPrdtId, productData, panelType, themeData] @@ -636,10 +593,9 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { fp.pipe( () => ({ panelPrdtId, productData }), ({ panelPrdtId, productData }) => - fp.isNotNil(panelPrdtId) && - fp.pipe(() => productData, fp.get("prdtNm"), fp.isNotNil)() - ? fp.pipe(() => productData, fp.get("prdtNm"))() - : "" + fp.isNotNil(panelPrdtId) && fp.pipe(() => productData, fp.get('prdtNm'), fp.isNotNil)() + ? fp.pipe(() => productData, fp.get('prdtNm'))() + : '' )(), [panelPrdtId, productData] ); @@ -653,6 +609,49 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { }; }, []); + // 백그라운드 전체화면 비디오 제어: DetailPanel 진입/퇴장 시 + useEffect(() => { + // console.log('[BgVideo] DetailPanel mounted - checking panels:', { + // panelsCount: panels?.length, + // panels: panels?.map(p => ({ name: p.name, modal: p.panelInfo?.modal })) + // }); + + // 전체화면 PlayerPanel(modal=false)이 존재하는지 확인 + const hasFullscreenPlayerPanel = fp.pipe( + () => panels, + (panelList) => + panelList.some( + (panel) => panel.name === panel_names.PLAYER_PANEL && !panel.panelInfo?.modal + ) + )(); + + // ProductAllSection에 비디오가 있는지 확인 + const hasProductVideo = fp.pipe(() => productData, fp.get('prdtMediaUrl'), fp.isNotNil)(); + + // console.log('[BgVideo] hasFullscreenPlayerPanel:', hasFullscreenPlayerPanel); + // console.log('[BgVideo] hasProductVideo:', hasProductVideo); + + // 전체화면 PlayerPanel이 있고, 제품에 비디오가 있을 때만 백그라운드 비디오 멈춤 + if (hasFullscreenPlayerPanel && hasProductVideo) { + // console.log('[BgVideo] DetailPanel - Product has video, dispatching pauseFullscreenVideo()'); + dispatch(pauseFullscreenVideo()); + } else { + console.log('[BgVideo] DetailPanel - Skipping pause:', { + reason: !hasFullscreenPlayerPanel ? 'no fullscreen PlayerPanel' : 'no product video', + }); + } + + return () => { + // DetailPanel 언마운트 시: 비디오가 있었고 멈췄던 경우만 재생 재개 + // console.log('[BgVideo] DetailPanel unmounting'); + if (hasFullscreenPlayerPanel && hasProductVideo) { + // console.log('[BgVideo] DetailPanel - Product had video, dispatching resumeFullscreenVideo()'); + dispatch(resumeFullscreenVideo()); + } + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); // 마운트/언마운트 시에만 실행 + return (
@@ -687,12 +686,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { ({ isLoading, panelInfo, productDataSource, productType }) => { const hasRequiredData = fp.pipe( () => [panelInfo, productDataSource, productType], - (data) => - fp.reduce( - (acc, item) => acc && fp.isNotNil(item), - true, - data - ) + (data) => fp.reduce((acc, item) => acc && fp.isNotNil(item), true, data) )(); return { diff --git a/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v2.jsx b/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v2.jsx index 69626679..cae99d51 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v2.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v2.jsx @@ -17,6 +17,7 @@ import { stopAutoClose, resetAutoClose, } from '../../../../actions/videoOverlayActions'; +import { pauseFullscreenVideo, resumeFullscreenVideo } from '../../../../actions/playActions'; import css from './ProductVideo.module.less'; const SpottableComponent = Spottable('div'); @@ -143,7 +144,13 @@ export default function ProductVideoV2({ // 썸네일 클릭 핸들러 - 비디오 재생 시작 + Redux dispatch + MediaPlayer 메서드 호출 const handleThumbnailClick = useCallback(() => { if (canPlayVideo && !isPlaying) { + // console.log('[BgVideo] ProductVideoV2 - Starting video playback'); setIsPlaying(true); + + // 백그라운드 전체화면 비디오 일시정지 + // console.log('[BgVideo] ProductVideoV2 - Pausing background fullscreen video'); + dispatch(pauseFullscreenVideo()); + // Redux: 오버레이 상태 초기화 dispatch(showControls()); dispatch(startAutoClose(3000)); @@ -157,8 +164,14 @@ export default function ProductVideoV2({ // 비디오 종료 핸들러 - 썸네일로 복귀 + Redux cleanup + MediaPlayer 메서드 호출 const handleVideoEnded = useCallback(() => { + // console.log('[BgVideo] ProductVideoV2 - Video ended'); setIsPlaying(false); setIsFullscreen(false); // 전체화면도 해제 + + // 백그라운드 전체화면 비디오 재생 재개 + // console.log('[BgVideo] ProductVideoV2 - Resuming background fullscreen video'); + dispatch(resumeFullscreenVideo()); + // Redux: 오버레이 상태 정리 dispatch(stopAutoClose()); dispatch(hideControls()); @@ -185,12 +198,19 @@ export default function ProductVideoV2({ const handleBackButton = useCallback(() => { if (isFullscreen) { // 전체화면이면 일반 모드로 + // console.log('[BgVideo] ProductVideoV2 - Exiting fullscreen to normal mode'); setIsFullscreen(false); dispatch(switchToModal()); videoPlayerRef.current?.stopAutoCloseTimeout?.(); } else if (isPlaying) { // 일반 모드에서 재생 중이면 썸네일로 + // console.log('[BgVideo] ProductVideoV2 - Stopping video (back button)'); setIsPlaying(false); + + // 백그라운드 전체화면 비디오 재생 재개 + // console.log('[BgVideo] ProductVideoV2 - Resuming background fullscreen video'); + dispatch(resumeFullscreenVideo()); + dispatch(stopAutoClose()); dispatch(hideControls()); // MediaPlayer 직접 제어: autoClose 타이머 중지 및 controls 숨김 @@ -202,7 +222,7 @@ export default function ProductVideoV2({ // 사용자 활동 감지 시 autoClose 타이머 리셋 const handleUserActivity = useCallback(() => { if (isPlaying && !isFullscreen) { - console.log('[ProductVideoV2] User activity detected - resetting autoClose timer'); + // console.log('[ProductVideoV2] User activity detected - resetting autoClose timer'); dispatch(resetAutoClose()); videoPlayerRef.current?.startAutoCloseTimeout?.(); } @@ -268,7 +288,12 @@ export default function ProductVideoV2({ useEffect(() => { if (autoPlay && canPlayVideo && !isPlaying) { autoPlayTimerRef.current = setTimeout(() => { + // console.log('[BgVideo] ProductVideoV2 - AutoPlay starting'); setIsPlaying(true); + + // 백그라운드 전체화면 비디오 일시정지 + // console.log('[BgVideo] ProductVideoV2 - Pausing background fullscreen video (autoPlay)'); + dispatch(pauseFullscreenVideo()); }, 500); } @@ -279,7 +304,7 @@ export default function ProductVideoV2({ autoPlayTimerRef.current = null; } }; - }, [autoPlay, canPlayVideo, isPlaying]); + }, [autoPlay, canPlayVideo, isPlaying, dispatch]); // VideoPlayer wrapper를 적절한 container로 이동 useEffect(() => { @@ -317,14 +342,21 @@ export default function ProductVideoV2({ } }, [isFullscreen, isPlaying]); - // 컴포넌트 언마운트 시 Redux 상태 정리 + // 컴포넌트 언마운트 시 Redux 상태 정리 및 백그라운드 비디오 재생 재개 useEffect(() => { return () => { - console.log('[ProductVideoV2] Component unmounting - cleaning up Redux state'); + // console.log('[BgVideo] ProductVideoV2 - Component unmounting'); + + // 비디오가 재생 중이었다면 백그라운드 비디오 재생 재개 + if (isPlaying) { + // console.log('[BgVideo] ProductVideoV2 - Was playing, resuming background video'); + dispatch(resumeFullscreenVideo()); + } + dispatch(stopAutoClose()); dispatch(hideControls()); }; - }, [dispatch]); + }, [dispatch, isPlaying]); if (!canPlayVideo) return null; @@ -396,11 +428,9 @@ export default function ProductVideoV2({ {typeof window === 'object' && window.PalmSystem && ( )} - {productInfo?.prdtMediaSubtitlUrl && - typeof window === 'object' && - window.PalmSystem && ( - - )} + {productInfo?.prdtMediaSubtitlUrl && typeof window === 'object' && window.PalmSystem && ( + + )}
) : null; diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx index 0f7c638e..e8e97829 100644 --- a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx +++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx @@ -356,16 +356,28 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props // 새로운 useEffect 추가 (라인 328 이후) useEffect(() => { - if (panelInfo?.modal && videoPlayer.current) { - if (panelInfo.isPaused) { - console.log('[PlayerPanel] Executing pause via videoPlayer.current'); + // console.log('[BgVideo] PlayerPanel useEffect - panelInfo:', { + // modal: panelInfo?.modal, + // isPaused: panelInfo?.isPaused, + // hasVideoPlayer: !!videoPlayer.current + // }); + + // modal 여부와 관계없이 videoPlayer가 있고 isPaused 값이 명시적일 때 제어 + if (videoPlayer.current && panelInfo?.isPaused !== undefined) { + if (panelInfo.isPaused === true) { + // console.log('[BgVideo] PlayerPanel - Executing pause via videoPlayer.current'); videoPlayer.current.pause(); } else if (panelInfo.isPaused === false) { - console.log('[PlayerPanel] Executing play via videoPlayer.current'); + // console.log('[BgVideo] PlayerPanel - Executing play via videoPlayer.current'); videoPlayer.current.play(); } + } else { + console.log('[BgVideo] PlayerPanel - Skipping video control:', { + hasVideoPlayer: !!videoPlayer.current, + isPausedValue: panelInfo?.isPaused, + }); } - }, [panelInfo?.isPaused, panelInfo?.modal]); + }, [panelInfo?.isPaused]); // creating live log params useEffect(() => {