diff --git a/com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.js b/com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.js index d9d87287..40522613 100644 --- a/com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.js +++ b/com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.js @@ -710,6 +710,10 @@ const VideoPlayerBase = class extends React.Component { reactPlayerConfig: PropTypes.any, //for ReactPlayer qrCurrentItem: PropTypes.any, modalScale: PropTypes.number, + setBelowContentsVisible: PropTypes.func, + belowContentsVisible: PropTypes.bool, + tabContainerVersion: PropTypes.number, + tabIndexV2: PropTypes.number, }; static contextType = FloatingLayerContext; @@ -854,6 +858,29 @@ const VideoPlayerBase = class extends React.Component { this.showControls(); } } + + // TabContainerV2와 mediaControls 동기화 + if ( + this.props.tabContainerVersion === 2 && + this.props.belowContentsVisible !== undefined && + prevProps.belowContentsVisible !== this.props.belowContentsVisible + ) { + console.log('[VideoPlayer] belowContentsVisible 변경 감지:', { + prev: prevProps.belowContentsVisible, + current: this.props.belowContentsVisible, + mediaControlsVisible: this.state.mediaControlsVisible, + }); + + if (this.props.belowContentsVisible && !this.state.mediaControlsVisible) { + // TabContainerV2가 표시될 때 controls도 표시 + console.log('[VideoPlayer] TabContainerV2 표시 - controls 강제 표시'); + this.showControls(); + } else if (!this.props.belowContentsVisible && this.state.mediaControlsVisible) { + // TabContainerV2가 숨겨질 때 controls도 숨김 + console.log('[VideoPlayer] TabContainerV2 숨김 - controls 숨김'); + this.hideControls(); + } + } if ( (!this.state.mediaControlsVisible && prevState.mediaControlsVisible !== this.state.mediaControlsVisible) || @@ -1016,6 +1043,13 @@ const VideoPlayerBase = class extends React.Component { // If this.state.more is used as a reference for when this function should fire, timing for // detection of when "more" is pressed vs when the state is updated is mismatched. Using an // instance variable that's only set and used for this express purpose seems cleanest. + + // TabContainerV2가 표시 중이면 자동으로 닫지 않음 + if (this.props.tabContainerVersion === 2 && this.props.belowContentsVisible) { + console.log('[VideoPlayer] TabContainerV2 표시 중 - autoClose 비활성화'); + return; + } + if (this.props.autoCloseTimeout && !this.props.sideContentsVisible) { this.autoCloseJob.startAfter(this.props.autoCloseTimeout); } @@ -1947,10 +1981,13 @@ const VideoPlayerBase = class extends React.Component { return; } - // TabContainerV2 토글 - if (this.props.setBelowContentsVisible && this.props.belowContentsVisible !== undefined) { - console.log('[VideoPlayer] belowContentsVisible 토글:', !this.props.belowContentsVisible); - this.props.setBelowContentsVisible(!this.props.belowContentsVisible); + // tabContainerVersion === 2일 때 belowContentsVisible도 함께 토글 + if (this.props.tabContainerVersion === 2 && this.props.setBelowContentsVisible) { + const willShowControls = !this.state.mediaControlsVisible; + console.log('[VideoPlayer] 클릭 - 상태 동기화 토글:', willShowControls); + + // belowContentsVisible을 먼저 변경 (componentDidUpdate에서 mediaControls 동기화됨) + this.props.setBelowContentsVisible(willShowControls); } this.toggleControls(); @@ -2152,6 +2189,8 @@ const VideoPlayerBase = class extends React.Component { isYoutube, sideContentsVisible, setSideContentsVisible, + belowContentsVisible, + tabContainerVersion, disclaimer, liveTotalTime, currentLiveTimeSeconds, @@ -2196,6 +2235,10 @@ const VideoPlayerBase = class extends React.Component { delete mediaProps.thumbnailUnavailable; delete mediaProps.titleHideDelay; delete mediaProps.videoPath; + delete mediaProps.setBelowContentsVisible; + delete mediaProps.belowContentsVisible; + delete mediaProps.tabContainerVersion; + delete mediaProps.tabIndexV2; mediaProps.autoPlay = !noAutoPlay; mediaProps.className = type !== 'MEDIA' ? css.video : css.media; @@ -2393,6 +2436,9 @@ const VideoPlayerBase = class extends React.Component { setIsSubtitleActive={setIsSubtitleActive} sideContentsVisible={sideContentsVisible} setSideContentsVisible={setSideContentsVisible} + belowContentsVisible={belowContentsVisible} + tabContainerVersion={tabContainerVersion} + tabIndexV2={this.props.tabIndexV2} videoVerticalVisible={videoVerticalVisible} handleIndicatorUpClick={handleIndicatorUpClick} handleIndicatorDownClick={handleIndicatorDownClick} diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerOverlay/PlayerOverlayContents.jsx b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerOverlay/PlayerOverlayContents.jsx index e81f155f..8dc7c684 100644 --- a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerOverlay/PlayerOverlayContents.jsx +++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerOverlay/PlayerOverlayContents.jsx @@ -1,27 +1,24 @@ -import React, { useCallback, useEffect, useMemo, useRef } from "react"; +import React, { useCallback, useEffect, useMemo, useRef } from 'react'; -import classNames from "classnames"; -import { useDispatch, useSelector } from "react-redux"; +import classNames from 'classnames'; +import { useDispatch, useSelector } from 'react-redux'; -import Spotlight from "@enact/spotlight"; -import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator"; -import Spottable from "@enact/spotlight/Spottable"; -import Marquee from "@enact/ui/Marquee"; +import Spotlight from '@enact/spotlight'; +import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator'; +import Spottable from '@enact/spotlight/Spottable'; +import Marquee from '@enact/ui/Marquee'; -import defaultLogoImg from "../../../../assets/images/ic-tab-partners-default@3x.png"; -import { setShowPopup } from "../../../actions/commonActions"; -import CustomImage from "../../../components/CustomImage/CustomImage"; -import { ACTIVE_POPUP } from "../../../utils/Config"; -import { SpotlightIds } from "../../../utils/SpotlightIds"; -import PlayerTabButton from "../PlayerTabContents/TabButton/PlayerTabButton"; -import css from "./PlayerOverlayContents.module.less"; +import defaultLogoImg from '../../../../assets/images/ic-tab-partners-default@3x.png'; +import { setShowPopup } from '../../../actions/commonActions'; +import CustomImage from '../../../components/CustomImage/CustomImage'; +import { ACTIVE_POPUP } from '../../../utils/Config'; +import { SpotlightIds } from '../../../utils/SpotlightIds'; +import PlayerTabButton from '../PlayerTabContents/TabButton/PlayerTabButton'; +import css from './PlayerOverlayContents.module.less'; -const SpottableBtn = Spottable("button"); +const SpottableBtn = Spottable('button'); -const Container = SpotlightContainerDecorator( - { enterTo: "default-element" }, - "div" -); +const Container = SpotlightContainerDecorator({ enterTo: 'default-element' }, 'div'); export default function PlayerOverlayContents({ type, @@ -35,8 +32,11 @@ export default function PlayerOverlayContents({ videoVerticalVisible, sideContentsVisible, setSideContentsVisible, + belowContentsVisible, handleIndicatorUpClick, handleIndicatorDownClick, + tabContainerVersion, + tabIndexV2, ...rest }) { const cntry_cd = useSelector((state) => state.common.httpHeader?.cntry_cd); @@ -49,7 +49,7 @@ export default function PlayerOverlayContents({ const backBtnRef = useRef(null); useEffect(() => { - if (type === "MEDIA" && !panelInfo.modal && backBtnRef.current) { + if (type === 'MEDIA' && !panelInfo.modal && backBtnRef.current) { Spotlight.focus(SpotlightIds.PLAYER_BACK_BUTTON); } }, [panelInfo?.shptmBanrTpNm, panelInfo.modal, backBtnRef.current]); @@ -64,7 +64,7 @@ export default function PlayerOverlayContents({ const patncLogoPath = useMemo(() => { let logo = playListInfo[selectedIndex]?.patncLogoPath; - if (type === "MEDIA") { + if (type === 'MEDIA') { logo = panelInfo?.patncLogoPath; } @@ -73,7 +73,7 @@ export default function PlayerOverlayContents({ const partnerName = useMemo(() => { let name = playListInfo[selectedIndex]?.patncNm; - if (type === "MEDIA") { + if (type === 'MEDIA') { name = panelInfo?.patncNm; } @@ -82,11 +82,11 @@ export default function PlayerOverlayContents({ const showName = useMemo(() => { let name = playListInfo[selectedIndex]?.showNm; - if (type === "MEDIA") { + if (type === 'MEDIA') { name = panelInfo?.showNm; } - return name ? name.replace(//gi, " ") : ""; + return name ? name.replace(//gi, ' ') : ''; }, [playListInfo, selectedIndex, panelInfo]); const onSpotlightMoveTabButton = (e) => { @@ -97,15 +97,15 @@ export default function PlayerOverlayContents({ const onSpotlightMoveMediaButton = (e) => { e.stopPropagation(); - if (type === "LIVE") { - return Spotlight.focus("videoIndicator-down-button"); + if (type === 'LIVE') { + return Spotlight.focus('videoIndicator-down-button'); } - Spotlight.focus("videoPlayer_mediaControls"); + Spotlight.focus('videoPlayer_mediaControls'); }; const onSpotlightMoveSlider = useCallback( (e) => { - if (type === "VOD") { + if (type === 'VOD') { e.stopPropagation(); Spotlight.focus(SpotlightIds.PLAYER_SLIDER); @@ -117,7 +117,24 @@ export default function PlayerOverlayContents({ const onSpotlightMoveSideTab = (e) => { e.stopPropagation(); e.preventDefault(); - Spotlight.focus("tab-0"); + Spotlight.focus('tab-0'); + }; + + const onSpotlightMoveBelowTab = (e) => { + e.stopPropagation(); + e.preventDefault(); + + // tabIndexV2에 따라 다른 버튼으로 포커스 이동 + if (tabIndexV2 === 0) { + // ShopNow 탭: Close 버튼으로 + Spotlight.focus('below-tab-close-button'); + } else if (tabIndexV2 === 1) { + // LIVE CHANNEL 탭: LIVE CHANNEL 버튼으로 + Spotlight.focus('below-tab-live-channel-button'); + } else if (tabIndexV2 === 2) { + // ShopNowButton: ShopNowButton으로 + Spotlight.focus('below-tab-shop-now-button'); + } }; const onSpotlightMoveBackButton = () => { @@ -125,11 +142,16 @@ export default function PlayerOverlayContents({ }; const currentSideButtonStatus = useMemo(() => { - if (type !== "MEDIA" && !panelInfo?.modal && !sideContentsVisible) { + if ( + type !== 'MEDIA' && + !panelInfo?.modal && + !sideContentsVisible && + tabContainerVersion === 1 + ) { return true; } return false; - }, [panelInfo, sideContentsVisible]); + }, [panelInfo, sideContentsVisible, tabContainerVersion]); const noLiveContentsVisible = useMemo(() => { if (!Array.isArray(playListInfo) || playListInfo.length === 0) { @@ -147,54 +169,63 @@ export default function PlayerOverlayContents({ return ( <> - {type !== "MEDIA" && - playListInfo.length > 1 && - noLiveContentsVisible && ( - <> -
- -
-
- -
- - )} + {type !== 'MEDIA' && playListInfo.length > 1 && noLiveContentsVisible && ( + <> +
+ +
+
+ +
+ + )} {currentSideButtonStatus && !videoVerticalVisible && ( )} - {cntry_cd === "US" && ( + {cntry_cd === 'US' && (
{partnerName && ( @@ -235,17 +270,14 @@ export default function PlayerOverlayContents({

{partnerName}

{showName}
- {type === "VOD" && disclaimer && ( + {type === 'VOD' && disclaimer && (

{disclaimer}

diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx index cf9679a0..ff41e045 100644 --- a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx +++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx @@ -971,7 +971,11 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props } if (!panelInfo.modal && !videoVerticalVisible && !hasProperSpot) { - Spotlight.focus(SpotlightIds.PLAYER_TAB_BUTTON); + if (tabContainerVersion === 1) { + Spotlight.focus(SpotlightIds.PLAYER_TAB_BUTTON); + } else if (tabContainerVersion === 2) { + Spotlight.focus('below-tab-live-channel-button'); + } return; } //비디오 진입시 포커스 @@ -986,8 +990,8 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props panelInfo.modal, panelInfo.isUpdatedByClick, panelInfo.isIndicatorByClick, - panelInfo.shptmBanrTpNm, + tabContainerVersion, ]); // 최상단 패널 정보 (여러 useMemo에서 공통으로 사용) @@ -1943,10 +1947,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props console.log('[PlayerPanel] isOnTop true - 오버레이 표시'); setSideContentsVisible(true); setBelowContentsVisible(true); - - if (videoPlayer.current?.showControls) { - videoPlayer.current.showControls(); - } + // VideoPlayer가 belowContentsVisible prop을 감지해서 자동으로 controls 표시함 } }, [isOnTop, panelInfo.modal, videoVerticalVisible]); @@ -2165,12 +2166,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props noAutoPlay={cannotPlay} autoCloseTimeout={3000} onBackButton={onClickBack} - spotlightDisabled={ - (!videoVerticalVisible && - panelInfo?.shptmBanrTpNm !== 'MEDIA' && - sideContentsVisible) || - panelInfo.modal - } + spotlightDisabled={panelInfo.modal} isYoutube={isYoutube} src={currentPlayingUrl} style={panelInfo.modal ? modalStyle : {}} @@ -2211,6 +2207,8 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props setCurrentTime={setCurrentTime} setIsVODPaused={setIsVODPaused} broadcast={broadcast} + tabContainerVersion={tabContainerVersion} + tabIndexV2={tabIndexV2} > {typeof window === 'object' && window.PalmSystem && ( diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/LiveChannelContents.jsx b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/LiveChannelContents.jsx index 1bc418c0..41615161 100644 --- a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/LiveChannelContents.jsx +++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/LiveChannelContents.jsx @@ -119,6 +119,16 @@ export default function LiveChannelContents({ patnerName={patncNm} onClick={handleItemClick} onFocus={handleFocus()} + onSpotlightUp={ + version === 2 && index === 0 + ? (e) => { + // v2에서 첫 번째 아이템일 때 위로 가면 LIVE CHANNEL 버튼으로 + e.stopPropagation(); + e.preventDefault(); + Spotlight.focus('below-tab-live-channel-button'); + } + : undefined + } type={TYPES.liveHorizontal} spotlightId={`tabChannel-video-${index}`} liveInfo={liveInfos[index]} diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/ShopNowContents.jsx b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/ShopNowContents.jsx index 77dea663..cae74c05 100644 --- a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/ShopNowContents.jsx +++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/ShopNowContents.jsx @@ -3,6 +3,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useDispatch, useSelector } from 'react-redux'; import { Job } from '@enact/core/util'; +import Spotlight from '@enact/spotlight'; import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator'; import { getContainerNode, setContainerLastFocusedElement } from '@enact/spotlight/src/container'; @@ -169,6 +170,17 @@ export default function ShopNowContents({ productId={prdtId} onClick={handleItemClick} onFocus={handleFocus()} + spotlightId={`shop-now-item-${index}`} + onSpotlightUp={ + version === 2 && index === 0 + ? (e) => { + // v2에서 첫 번째 아이템일 때 위로 가면 Close 버튼으로 + e.stopPropagation(); + e.preventDefault(); + Spotlight.focus('below-tab-close-button'); + } + : undefined + } type={TYPES.horizontal} version={version} /> diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/ShopNowButton.jsx b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/ShopNowButton.jsx index 4765c572..ebef2735 100644 --- a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/ShopNowButton.jsx +++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/ShopNowButton.jsx @@ -1,15 +1,26 @@ import React from 'react'; +import Spotlight from '@enact/spotlight'; import Spottable from '@enact/spotlight/Spottable'; +import { SpotlightIds } from '../../../../utils/SpotlightIds'; import css from './ShopNowButton.module.less'; -const SpottableDiv = Spottable("div"); +const SpottableDiv = Spottable('div'); export default function ShopNowButton({ onClick }) { return ( -
- +
+ { + e.stopPropagation(); + e.preventDefault(); + Spotlight.focus(SpotlightIds.PLAYER_BACK_BUTTON); + }} + > SHOP NOW
diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.jsx b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.jsx index bf0c1218..1930eb3a 100644 --- a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.jsx +++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.jsx @@ -1,33 +1,25 @@ -import React, { - useCallback, - useEffect, -} from 'react'; +import React, { useCallback, useEffect } from 'react'; import classNames from 'classnames'; import Spotlight from '@enact/spotlight'; -import SpotlightContainerDecorator - from '@enact/spotlight/SpotlightContainerDecorator'; +import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator'; import Spottable from '@enact/spotlight/Spottable'; -import icon_arrow_dwon - from '../../../../../assets/images/player/icon_tabcontainer_arrow_down.png'; -import icon_shop_now - from '../../../../../assets/images/player/icon_tabcontainer_shopnow.png'; +import icon_arrow_dwon from '../../../../../assets/images/player/icon_tabcontainer_arrow_down.png'; +import icon_shop_now from '../../../../../assets/images/player/icon_tabcontainer_shopnow.png'; import { LOG_MENU } from '../../../../utils/Config'; import { $L } from '../../../../utils/helperMethods'; +import { SpotlightIds } from '../../../../utils/SpotlightIds'; import LiveChannelContents from '../TabContents/LiveChannelContents'; import ShopNowContents from '../TabContents/ShopNowContents'; import YouMayLikeContents from '../TabContents/YouMayLikeContents'; import ShopNowButton from './ShopNowButton'; import css from './TabContainer.v2.module.less'; -const Container = SpotlightContainerDecorator( - { enterTo: "last-focused" }, - "div" -); +const Container = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div'); -const SpottableDiv = Spottable("div"); +const SpottableDiv = Spottable('div'); export default function TabContainerV2({ panelInfo, @@ -48,10 +40,8 @@ export default function TabContainerV2({ tabVisible, }) { const tabList = [ - $L("SHOP NOW"), - panelInfo?.shptmBanrTpNm === "LIVE" - ? $L("LIVE CHANNEL") - : $L("FEATURED SHOWS"), + $L('SHOP NOW'), + panelInfo?.shptmBanrTpNm === 'LIVE' ? $L('LIVE CHANNEL') : $L('FEATURED SHOWS'), ]; useEffect(() => { @@ -62,10 +52,8 @@ export default function TabContainerV2({ } if (tabIndex === 1) { - const isLive = panelInfo?.shptmBanrTpNm === "LIVE"; - nowMenu = isLive - ? LOG_MENU.FULL_LIVE_CHANNELS - : LOG_MENU.FULL_FEATURED_SHOWS; + const isLive = panelInfo?.shptmBanrTpNm === 'LIVE'; + nowMenu = isLive ? LOG_MENU.FULL_LIVE_CHANNELS : LOG_MENU.FULL_FEATURED_SHOWS; } if (nowMenu) { @@ -87,7 +75,7 @@ export default function TabContainerV2({ if (videoVerticalVisible) { e.stopPropagation(); e.preventDefault(); - Spotlight.focus("spotlightId-video-contaienr"); + Spotlight.focus('spotlightId-video-contaienr'); } }, [videoVerticalVisible] @@ -98,12 +86,22 @@ export default function TabContainerV2({ e.stopPropagation(); e.preventDefault(); if (onTabClose) { - onTabClose(1); // tabIndex를 -1로 설정 + onTabClose(1); // tabIndex를 1로 설정 } }, [onTabClose] ); + // 위 방향 포커스 이동 시 백 버튼으로 이동 + const handleSpotlightUpToBackButton = useCallback((e) => { + e.stopPropagation(); + e.preventDefault(); + + // VideoPlayer가 belowContentsVisible prop을 감지해서 이미 controls를 표시했으므로 + // 바로 포커스 이동 + Spotlight.focus(SpotlightIds.PLAYER_BACK_BUTTON); + }, []); + // useEffect(()=>{ // console.log('[tabIndex]',tabIndex) // },[tabIndex]) @@ -122,17 +120,21 @@ export default function TabContainerV2({
- shop now icon + shop now icon
SHOP NOW
{ + // 첫 번째 ShopNow 아이템으로 포커스 이동 + e.stopPropagation(); + e.preventDefault(); + Spotlight.focus('shop-now-item-0'); + }} > × @@ -166,6 +168,11 @@ export default function TabContainerV2({ className={css.liveChannelButton} onClick={onLiveChannelButtonClick} spotlightId="below-tab-live-channel-button" + onSpotlightUp={handleSpotlightUpToBackButton} + onSpotlightDown={(e) => { + // 첫 번째 PlayerItem으로 포커스 이동 + Spotlight.focus('tabChannel-video-0'); + }} > LIVE CHANNEL
@@ -173,7 +180,7 @@ export default function TabContainerV2({
- {panelInfo?.shptmBanrTpNm === "LIVE" && playListInfo && ( + {panelInfo?.shptmBanrTpNm === 'LIVE' && playListInfo && ( )} - {tabVisible && tabIndex === 2 && ( - - )} + {tabVisible && tabIndex === 2 && } ); }