🕐 커밋 시간: 2025. 11. 24. 12:13:08 📊 변경 통계: • 총 파일: 10개 • 추가: +29줄 • 삭제: -110줄 📁 추가된 파일: + com.twin.app.shoptime/src/utils/debug.js 📝 수정된 파일: ~ com.twin.app.shoptime/src/actions/playActions.js ~ com.twin.app.shoptime/src/actions/productActions.js ~ com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.js ~ com.twin.app.shoptime/src/hooks/useFocusHistory/useFocusHistory.js ~ com.twin.app.shoptime/src/lunaSend/common.js ~ com.twin.app.shoptime/src/reducers/panelReducer.js ~ com.twin.app.shoptime/src/views/HomePanel/HomePanel.jsx ~ com.twin.app.shoptime/src/views/MyPagePanel/MyPageSub/TermsOfService/TermsOfOptional.jsx ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx 🔧 함수 변경 내용: 📄 com.twin.app.shoptime/src/actions/playActions.js (javascript): ❌ Deleted: dwarn(), derror() 📄 com.twin.app.shoptime/src/actions/productActions.js (javascript): ❌ Deleted: dwarn(), derror() 📄 com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.js (javascript): ❌ Deleted: dwarn(), derror() 📄 com.twin.app.shoptime/src/hooks/useFocusHistory/useFocusHistory.js (javascript): ❌ Deleted: dwarn(), derror() 📄 com.twin.app.shoptime/src/lunaSend/common.js (javascript): ❌ Deleted: dwarn(), derror() 📄 com.twin.app.shoptime/src/reducers/panelReducer.js (javascript): ❌ Deleted: dwarn(), derror() 📄 com.twin.app.shoptime/src/views/HomePanel/HomePanel.jsx (javascript): ❌ Deleted: dwarn(), derror() 📄 com.twin.app.shoptime/src/views/MyPagePanel/MyPageSub/TermsOfService/TermsOfOptional.jsx (javascript): ❌ Deleted: dwarn(), derror() 📄 com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx (javascript): ❌ Deleted: dwarn(), derror() 🔧 주요 변경 내용: • 핵심 비즈니스 로직 개선 • UI 컴포넌트 아키텍처 개선 • API 서비스 레이어 개선 • 공통 유틸리티 함수 최적화 Performance: 코드 최적화로 성능 개선 기대
2969 lines
95 KiB
JavaScript
2969 lines
95 KiB
JavaScript
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react';
|
|
|
|
import classNames from 'classnames';
|
|
import { useDispatch } from 'react-redux';
|
|
|
|
import { Job } from '@enact/core/util';
|
|
import Spotlight from '@enact/spotlight';
|
|
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
|
|
import { setContainerLastFocusedElement } from '@enact/spotlight/src/container';
|
|
|
|
import dummyVtt from '../../../assets/mock/video.vtt';
|
|
import { createDebugHelpers } from '../../utils/debug';
|
|
import {
|
|
changeAppStatus,
|
|
changeLocalSettings,
|
|
requestLiveSubtitle,
|
|
sendBroadCast,
|
|
setHidePopup,
|
|
} from '../../actions/commonActions';
|
|
import {
|
|
sendLogGNB,
|
|
sendLogLive,
|
|
sendLogTotalRecommend,
|
|
sendLogVOD,
|
|
} from '../../actions/logActions';
|
|
import {
|
|
clearShopNowInfo,
|
|
getHomeFullVideoInfo,
|
|
getMainCategoryShowDetail,
|
|
getMainLiveShow,
|
|
getMainLiveShowNowProduct,
|
|
} from '../../actions/mainActions';
|
|
import * as PanelActions from '../../actions/panelActions';
|
|
import { updatePanel } from '../../actions/panelActions';
|
|
import {
|
|
CLEAR_PLAYER_INFO,
|
|
getChatLog,
|
|
getSubTitle,
|
|
startVideoPlayer,
|
|
pauseModalVideo,
|
|
resumeModalVideo,
|
|
resumeFullscreenVideo,
|
|
updateVideoPlayState,
|
|
} from '../../actions/playActions';
|
|
import { resetPlayerOverlays } from '../../actions/videoPlayActions';
|
|
import { convertUtcToLocal } from '../../components/MediaPlayer/util';
|
|
import TPanel from '../../components/TPanel/TPanel';
|
|
import TPopUp from '../../components/TPopUp/TPopUp';
|
|
import Media from '../../components/VideoPlayer/Media';
|
|
import TReactPlayer from '../../components/VideoPlayer/TReactPlayer';
|
|
import { VideoPlayer } from '../../components/VideoPlayer/VideoPlayer';
|
|
import useWhyDidYouUpdate from '../../hooks/useWhyDidYouUpdate';
|
|
import * as Config from '../../utils/Config';
|
|
import { ACTIVE_POPUP, panel_names } from '../../utils/Config';
|
|
import { $L, formatGMTString } from '../../utils/helperMethods';
|
|
import { SpotlightIds } from '../../utils/SpotlightIds';
|
|
import { removeDotAndColon } from './PlayerItemCard/PlayerItemCard';
|
|
import PlayerOverlayChat from './PlayerOverlay/PlayerOverlayChat';
|
|
import PlayerOverlayQRCode from './PlayerOverlay/PlayerOverlayQRCode';
|
|
import css from './PlayerPanel.module.less';
|
|
import PlayerTabButton from './PlayerTabContents/TabButton/PlayerTabButton';
|
|
import TabContainer from './PlayerTabContents/TabContainer';
|
|
import TabContainerV2 from './PlayerTabContents/v2/TabContainer.v2';
|
|
// import LiveShowContainer from './PlayerTabContents/v2/LiveShowContainer';
|
|
// import ShopNowContainer from './PlayerTabContents/v2/ShopNowContainer';
|
|
// import ShopNowButton from './PlayerTabContents/v2/ShopNowButton';
|
|
|
|
const Container = SpotlightContainerDecorator({ enterTo: 'last-focused', preserveId: true }, 'div');
|
|
|
|
// 디버그 헬퍼 설정
|
|
const DEBUG_MODE = false;
|
|
const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE);
|
|
|
|
const findSelector = (selector, maxAttempts = 5, currentAttempts = 0) => {
|
|
try {
|
|
if (currentAttempts >= maxAttempts) {
|
|
throw new Error('selector not found');
|
|
}
|
|
|
|
const initialSelector = document.querySelector(selector);
|
|
|
|
if (initialSelector) {
|
|
return initialSelector;
|
|
} else {
|
|
return findSelector(selector, maxAttempts, currentAttempts + 1);
|
|
}
|
|
} catch (error) {
|
|
// console.error(error.message);
|
|
}
|
|
};
|
|
|
|
const getLogTpNo = (type, nowMenu) => {
|
|
if (type === 'LIVE') {
|
|
switch (nowMenu) {
|
|
case Config.LOG_MENU.HOME_TOP:
|
|
return Config.LOG_TP_NO.LIVE.HOME;
|
|
// case Config.LOG_MENU.FEATURED_BRANDS_LIVE_CHANNELS:
|
|
// return Config.LOG_TP_NO.LIVE.FEATURED_BRANDS;
|
|
case Config.LOG_MENU.FULL_SHOP_NOW:
|
|
case Config.LOG_MENU.FULL_YOU_MAY_LIKE:
|
|
case Config.LOG_MENU.FULL_LIVE_CHANNELS:
|
|
return Config.LOG_TP_NO.LIVE.FULL;
|
|
default:
|
|
return Config.LOG_TP_NO.LIVE.FEATURED_BRANDS;
|
|
}
|
|
} else if (type === 'VOD') {
|
|
switch (nowMenu) {
|
|
case Config.LOG_MENU.HOME_TOP:
|
|
return Config.LOG_TP_NO.VOD.HOME_VOD; // 153
|
|
case Config.LOG_MENU.TRENDING_NOW_POPULAR_SHOWS:
|
|
return Config.LOG_TP_NO.VOD.POPULAR_SHOWS_AND_HOT_PICKS; // 151
|
|
case Config.LOG_MENU.FULL_SHOP_NOW:
|
|
case Config.LOG_MENU.FULL_YOU_MAY_LIKE:
|
|
case Config.LOG_MENU.FULL_FEATURED_SHOWS:
|
|
return Config.LOG_TP_NO.VOD.FULL_VOD; // 150
|
|
default:
|
|
return;
|
|
}
|
|
} else if (type === 'MEDIA') {
|
|
switch (nowMenu) {
|
|
case Config.LOG_MENU.HOME_TOP:
|
|
return Config.LOG_TP_NO.VOD.HOME_VOD; // 153
|
|
case Config.LOG_MENU.HOT_PICKS:
|
|
return Config.LOG_TP_NO.VOD.POPULAR_SHOWS_AND_HOT_PICKS; // 151
|
|
case Config.LOG_MENU.DETAIL_PAGE_BILLING_PRODUCT_DETAIL:
|
|
case Config.LOG_MENU.DETAIL_PAGE_PRODUCT_DETAIL:
|
|
case Config.LOG_MENU.DETAIL_PAGE_GROUP_DETAIL:
|
|
case Config.LOG_MENU.DETAIL_PAGE_THEME_DETAIL:
|
|
case Config.LOG_MENU.DETAIL_PAGE_TRAVEL_THEME_DETAIL:
|
|
return Config.LOG_TP_NO.VOD.ITEM_DETAIL_MEDIA; // 156
|
|
case Config.LOG_MENU.FULL:
|
|
return Config.LOG_TP_NO.VOD.FULL_MEDIA; // 155
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
};
|
|
|
|
const YOUTUBECONFIG = {
|
|
playerVars: {
|
|
controls: 0, // 플레이어 컨트롤 표시
|
|
autoplay: 1,
|
|
disablekb: 1,
|
|
enablejsapi: 1,
|
|
listType: 'user_uploads',
|
|
fs: 0,
|
|
rel: 0, // 관련 동영상 표시 안 함
|
|
showinfo: 0,
|
|
loop: 0,
|
|
iv_load_policy: 3,
|
|
modestbranding: 1,
|
|
wmode: 'opaque',
|
|
cc_lang_pref: 'en',
|
|
cc_load_policy: 0,
|
|
playsinline: 1,
|
|
},
|
|
};
|
|
|
|
const INITIAL_TIMEOUT = 30000;
|
|
const REGULAR_TIMEOUT = 30000;
|
|
const TAB_CONTAINER_SPOTLIGHT_ID = 'tab-container-spotlight-id';
|
|
const TAB_CONTAINER_V2_SPOTLIGHT_ID = 'tab-container-v2-spotlight-id';
|
|
const TARGET_EVENTS = ['mousemove', 'keydown', 'click'];
|
|
|
|
// last time error
|
|
const VIDEO_END_ACTION_DELAY = 1500;
|
|
|
|
const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props }) => {
|
|
const dispatch = useDispatch();
|
|
const { USE_STATE, USE_SELECTOR } = useWhyDidYouUpdate(spotlightId, {
|
|
isTabActivated,
|
|
panelInfo,
|
|
isOnTop,
|
|
...props,
|
|
});
|
|
|
|
const videoPlayer = useRef(null);
|
|
const focusReturnRef = useRef(null);
|
|
const modalPrevRef = useRef(panelInfo?.modal);
|
|
const prevIsTopPanelDetailFromPlayerRef = useRef(false);
|
|
const [playListInfo, setPlayListInfo] = USE_STATE('playListInfo', '');
|
|
const [shopNowInfo, setShopNowInfo] = USE_STATE('shopNowInfo');
|
|
const [backupInitialIndex, setBackupInitialIndex] = USE_STATE('backupInitialIndex', 0);
|
|
const [modalStyle, setModalStyle] = USE_STATE('modalStyle', {});
|
|
const [modalScale, setModalScale] = USE_STATE('modalScale', 1);
|
|
const [mediaId, setMediaId] = USE_STATE('mediaId', null);
|
|
const [currentLiveTimeSeconds, setCurrentLiveTimeSeconds] = USE_STATE(
|
|
'currentLiveTimeSeconds',
|
|
1
|
|
);
|
|
const [prevChannelIndex, setPrevChannelIndex] = USE_STATE('prevChannelIndex', 0);
|
|
const [sideContentsVisible, setSideContentsVisible] = USE_STATE('sideContentsVisible', true);
|
|
const [belowContentsVisible, setBelowContentsVisible] = USE_STATE('belowContentsVisible', true);
|
|
const [currentTime, setCurrentTime] = USE_STATE('currentTime', 0);
|
|
const [isInitialFocusOccurred, setIsInitialFocusOccurred] = USE_STATE(
|
|
'isInitialFocusOccurred',
|
|
false
|
|
);
|
|
const [selectedIndex, setSelectedIndex] = USE_STATE(
|
|
'selectedIndex',
|
|
panelInfo.shptmBanrTpNm === 'LIVE' ? null : 0
|
|
);
|
|
const [isUpdate, setIsUpdate] = USE_STATE('isUpdate', false);
|
|
const [isSubtitleActive, setIsSubtitleActive] = USE_STATE('isSubtitleActive', true);
|
|
const [logStatus, setLogStatus] = USE_STATE('logStatus', {
|
|
isModalLiveLogReady: false,
|
|
isFullLiveLogReady: false,
|
|
isDetailLiveLogReady: false,
|
|
isModalVodLogReady: false,
|
|
isFullVodLogReady: false,
|
|
isDetailVodLogReady: false,
|
|
isModalMediaLogReady: false,
|
|
isFullMediaLogReady: false,
|
|
isDetailMediaReady: false,
|
|
});
|
|
const [isVODPaused, setIsVODPaused] = USE_STATE('isVODPaused', false);
|
|
const [tabIndexV2, setTabIndexV2] = USE_STATE('tabIndexV2', 1); // 0: ShopNow, 1: LiveChannel, 2: ShopNowButton
|
|
const [tabContainerVersion, setTabContainerVersion] = USE_STATE('tabContainerVersion', 2); // 1: TabContainer (우측), 2: TabContainerV2 (하단)
|
|
const [isModalClosed, setIsModalClosed] = USE_STATE('isModalClosed', true); // 모달이 false 상태인지 나타내는 플래그
|
|
|
|
const panels = USE_SELECTOR('panels', (state) => state.panels.panels);
|
|
const chatData = USE_SELECTOR('chatData', (state) => state.play.chatData);
|
|
const shouldHideOverlays = USE_SELECTOR(
|
|
'shouldHideOverlays',
|
|
(state) => state.videoPlay.shouldHideOverlays
|
|
);
|
|
const shouldShowOverlays = USE_SELECTOR(
|
|
'shouldShowOverlays',
|
|
(state) => state.videoPlay.shouldShowOverlays
|
|
);
|
|
|
|
const popupVisible = USE_SELECTOR('popupVisible', (state) => state.common.popup.popupVisible);
|
|
const activePopup = USE_SELECTOR('activePopup', (state) => state.common.popup.activePopup);
|
|
const showDetailInfo = USE_SELECTOR('showDetailInfo', (state) => state.main.showDetailInfo);
|
|
const productImageLength = USE_SELECTOR(
|
|
'productImageLength',
|
|
(state) => state.product.productImageLength
|
|
);
|
|
const themeProductInfos = USE_SELECTOR(
|
|
'themeProductInfos',
|
|
(state) => state.home.themeCurationDetailInfoData
|
|
);
|
|
const hotelInfos = USE_SELECTOR('hotelInfos', (state) => state.home.themeCurationHotelDetailData);
|
|
const captionEnable = USE_SELECTOR(
|
|
'captionEnable',
|
|
(state) => state.common.appStatus.captionEnable
|
|
);
|
|
const fullVideolgCatCd = USE_SELECTOR('fullVideolgCatCd', (state) => state.main.fullVideolgCatCd);
|
|
const featuredShowsInfos = USE_SELECTOR(
|
|
'featuredShowsInfos',
|
|
(state) => state.main.featuredShowsInfos
|
|
);
|
|
const localRecentItems = USE_SELECTOR(
|
|
'localRecentItems',
|
|
(state) => state.localSettings?.recentItems
|
|
);
|
|
const httpHeader = USE_SELECTOR('httpHeader', (state) => state.common?.httpHeader);
|
|
const countryCode = USE_SELECTOR('countryCode', (state) => state.common.httpHeader?.cntry_cd);
|
|
const liveChannelInfos = USE_SELECTOR('liveChannelInfos', (state) => state.main.liveChannelInfos);
|
|
const showNowInfos = USE_SELECTOR('showNowInfos', (state) => state.main.showNowInfo);
|
|
const liveShowInfos = USE_SELECTOR('liveShowInfos', (state) => state.main.liveShowInfos);
|
|
const vodSubtitleData = USE_SELECTOR('vodSubtitleData', (state) => state.play.subTitleBlobs);
|
|
const broadcast = USE_SELECTOR('broadcast', (state) => state.common.broadcast);
|
|
const videoPlayState = USE_SELECTOR('videoPlayState', (state) => state.play.videoPlayState);
|
|
|
|
const lastPanelAction = USE_SELECTOR('lastPanelAction', (state) => state.panels.lastPanelAction);
|
|
const nowMenu = USE_SELECTOR('nowMenu', (state) => state.common.menu.nowMenu);
|
|
const nowMenuRef = useRef(null);
|
|
const entryMenu = USE_SELECTOR('entryMenu', (state) => state.common.menu.entryMenu);
|
|
const [videoLoaded, setVideoLoaded] = USE_STATE('videoLoaded', false);
|
|
const entryMenuRef = useRef(null);
|
|
const panelInfoRef = useRef(null);
|
|
|
|
const initialFocusTimeoutJob = useRef(new Job((func) => func(), 100));
|
|
const liveLogParamsRef = useRef(null);
|
|
const vodLogParamsRef = useRef(null);
|
|
const mediaLogParamsRef = useRef(null);
|
|
const prevNowMenuRef = useRef(null);
|
|
const watchIntervalLive = useRef(null);
|
|
const watchIntervalVod = useRef(null);
|
|
const watchIntervalMedia = useRef(null);
|
|
// useEffect(() => {
|
|
// console.log("###videoLoaded", videoLoaded);
|
|
// if (nowMenu) {
|
|
// }
|
|
// }, [videoLoaded]);
|
|
const currentLiveShowInfo = useMemo(() => {
|
|
if (liveShowInfos && liveShowInfos.length > 0) {
|
|
const panelInfoChanId = panelInfo?.chanId;
|
|
const isLive = panelInfo?.shptmBanrTpNm === 'LIVE';
|
|
|
|
if (isLive) {
|
|
const liveShowInfo = liveShowInfos //
|
|
.find(({ chanId }) => panelInfoChanId === chanId);
|
|
|
|
return liveShowInfo;
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
return {};
|
|
}, [liveShowInfos, panelInfo?.chanId, panelInfo?.shptmBanrTpNm]);
|
|
|
|
const currentVODShowInfo = useMemo(() => {
|
|
if (showDetailInfo && showDetailInfo.length > 0) {
|
|
const isVOD = panelInfo?.shptmBanrTpNm === 'VOD';
|
|
|
|
if (isVOD) {
|
|
const vodShowInfo = showDetailInfo[0];
|
|
|
|
return vodShowInfo;
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
return {};
|
|
}, [panelInfo?.shptmBanrTpNm, showDetailInfo]);
|
|
|
|
useEffect(() => {
|
|
if (!panelInfo?.modal && panelInfo?.shptmBanrTpNm === 'MEDIA') {
|
|
dispatch(sendLogGNB(Config.LOG_MENU.FULL));
|
|
prevNowMenuRef.current = nowMenuRef.current;
|
|
|
|
return () => dispatch(sendLogGNB(prevNowMenuRef.current));
|
|
} else if (panelInfo?.modal) {
|
|
dispatch(sendLogGNB(entryMenu));
|
|
}
|
|
}, [panelInfo?.modal, panelInfo?.shptmBanrTpNm]);
|
|
|
|
// useEffect(()=>{
|
|
// console.log('[PlayerPanel] isOnTop:', {
|
|
// isOnTop,
|
|
// panelInfo
|
|
// });
|
|
// if (panelInfo && panelInfo.modal) {
|
|
// if (!isOnTop) {
|
|
// console.log('[PlayerPanel] Not on top - pausing video');
|
|
// dispatch(pauseModalVideo());
|
|
// } else if (isOnTop && panelInfo.isPaused) {
|
|
// console.log('[PlayerPanel] Back on top - resuming video');
|
|
// dispatch(resumeModalVideo());
|
|
// }
|
|
// }
|
|
// },[isOnTop, panelInfo])
|
|
|
|
// useRef들 업데이트
|
|
// prevIsTopPanelDetailFromPlayerRef 업데이트
|
|
useEffect(() => {
|
|
prevIsTopPanelDetailFromPlayerRef.current = isTopPanelDetailFromPlayer;
|
|
}, [isTopPanelDetailFromPlayer]);
|
|
|
|
// nowMenuRef 업데이트
|
|
useEffect(() => {
|
|
nowMenuRef.current = nowMenu;
|
|
}, [nowMenu]);
|
|
|
|
// entryMenuRef 업데이트
|
|
useEffect(() => {
|
|
entryMenuRef.current = entryMenu;
|
|
}, [entryMenu]);
|
|
|
|
// panelInfoRef 업데이트
|
|
useEffect(() => {
|
|
panelInfoRef.current = panelInfo;
|
|
}, [panelInfo]);
|
|
|
|
// PlayerPanel.jsx의 라인 313-327 useEffect 수정 - detailPanelClosed flag 감지 추가
|
|
useEffect(() => {
|
|
dlog('[PlayerPanel] 🔍 isOnTop useEffect 호출:', {
|
|
isOnTop,
|
|
modal: panelInfo?.modal,
|
|
isPaused: panelInfo?.isPaused,
|
|
detailPanelClosed: panelInfo?.detailPanelClosed,
|
|
});
|
|
|
|
if (isOnTop) {
|
|
// 1. Resume Video if needed (isPaused or detailPanelClosed)
|
|
if (panelInfo.isPaused || panelInfo.detailPanelClosed) {
|
|
if (panelInfo.modal) {
|
|
dlog('[PlayerPanel] ▶️ Back on top (Modal) - resuming video');
|
|
dispatch(resumeModalVideo());
|
|
} else {
|
|
dlog('[PlayerPanel] ▶️ Back on top (Fullscreen) - resuming video');
|
|
dispatch(resumeFullscreenVideo());
|
|
}
|
|
}
|
|
|
|
// 2. Reset detailPanelClosed flag
|
|
if (panelInfo.detailPanelClosed) {
|
|
dlog('[PlayerPanel] 🔄 detailPanelClosed flag 초기화 시작');
|
|
dlog('[PlayerPanel] 🔙 DetailPanel에서 복귀 정보:', {
|
|
detailPanelClosedAt: panelInfo.detailPanelClosedAt,
|
|
detailPanelClosedFromSource: panelInfo.detailPanelClosedFromSource,
|
|
lastFocusedTargetId: panelInfo.lastFocusedTargetId,
|
|
});
|
|
|
|
// 포커스 복원 로직 추가 (1000ms 지연)
|
|
if (panelInfo.lastFocusedTargetId) {
|
|
dlog(
|
|
'[PlayerPanel] 🎯 DetailPanel 복귀 후 1000ms 지연 포커스 복원 예약:',
|
|
panelInfo.lastFocusedTargetId
|
|
);
|
|
|
|
const focusTimeoutId = setTimeout(() => {
|
|
dlog(
|
|
'[PlayerPanel] 🔍 DetailPanel 복귀 후 포커스 복원 실행:',
|
|
panelInfo.lastFocusedTargetId
|
|
);
|
|
Spotlight.focus(panelInfo.lastFocusedTargetId);
|
|
}, 1000);
|
|
|
|
// cleanup 함수를 통해 컴포넌트 unmount 시 타이머 정리
|
|
return () => {
|
|
if (focusTimeoutId) {
|
|
clearTimeout(focusTimeoutId);
|
|
dlog('[PlayerPanel] 🧹 포커스 복원 타이머 정리됨');
|
|
}
|
|
};
|
|
}
|
|
|
|
dispatch(
|
|
updatePanel({
|
|
name: panel_names.PLAYER_PANEL,
|
|
panelInfo: {
|
|
detailPanelClosed: false,
|
|
detailPanelClosedAt: undefined,
|
|
detailPanelClosedFromSource: undefined,
|
|
lastFocusedTargetId: undefined, // 포커스 복원 후 초기화
|
|
},
|
|
})
|
|
);
|
|
dlog('[PlayerPanel] 🏁 detailPanelClosed flag 초기화 완료');
|
|
}
|
|
} else {
|
|
// Not on top
|
|
if (panelInfo && panelInfo.modal) {
|
|
dlog('[PlayerPanel] ⏸️ Not on top (Modal) - pausing video logic commented out');
|
|
// dispatch(pauseModalVideo());
|
|
}
|
|
}
|
|
}, [isOnTop, panelInfo]);
|
|
|
|
// 이전 상태 저장을 위한 ref
|
|
const previousVideoPlayState = useRef(null);
|
|
const previousPanelInfo = useRef(null);
|
|
const previousVideoSource = useRef(null);
|
|
const previousIsOnTop = useRef(null);
|
|
|
|
// Redux 상태 변화 모니터링 useEffect (중요 변화만)
|
|
useEffect(() => {
|
|
// HomePanel이 최상위이고 videoPlayState가 실제로 변경되었을 때만 로그
|
|
const isOnTop = panel_names.HOME_PANEL === topPanel?.name;
|
|
const hasSignificantChange =
|
|
previousVideoPlayState.current?.isPlaying !== videoPlayState?.isPlaying ||
|
|
previousVideoPlayState.current?.isPaused !== videoPlayState?.isPaused ||
|
|
Math.abs(
|
|
(previousVideoPlayState.current?.currentTime || 0) - (videoPlayState?.currentTime || 0)
|
|
) > 1;
|
|
|
|
// 🔍 DetailPanel으로 인한 일시정지 상태 변화도 모니터링
|
|
const isDetailPanelOnTop = panel_names.DETAIL_PANEL === topPanel?.name;
|
|
const isPlayingChanged =
|
|
previousVideoPlayState.current?.isPlaying !== videoPlayState?.isPlaying;
|
|
const isPausedChanged = previousVideoPlayState.current?.isPaused !== videoPlayState?.isPaused;
|
|
|
|
if (
|
|
(isOnTop && videoPlayState && hasSignificantChange) ||
|
|
(isDetailPanelOnTop && (isPlayingChanged || isPausedChanged))
|
|
) {
|
|
dlog('📊 [PlayerPanel] Significant videoPlayState change', {
|
|
previousState: previousVideoPlayState.current,
|
|
currentState: videoPlayState,
|
|
topPanelName: topPanel?.name,
|
|
isOnTop,
|
|
isDetailPanelOnTop,
|
|
videoPlayerExists: !!videoPlayer.current,
|
|
currentPlayingUrl,
|
|
changeReason: isDetailPanelOnTop ? 'DetailPanel transition' : 'HomePanel state change',
|
|
// 🔍 Redux paused 상태 특별 확인
|
|
reduxIsPaused: videoPlayState?.isPaused,
|
|
reduxIsPlaying: videoPlayState?.isPlaying,
|
|
panelInfoIsPaused: panelInfo?.isPaused,
|
|
timestamp: new Date().toISOString(),
|
|
source: 'useEffect videoPlayState',
|
|
});
|
|
}
|
|
|
|
previousVideoPlayState.current = videoPlayState;
|
|
}, [videoPlayState, topPanel?.name, panelInfo?.isPaused]);
|
|
|
|
// PanelInfo 상태 변화 모니터링 useEffect (isPaused가 실제로 변경될 때만)
|
|
useEffect(() => {
|
|
const isOnTop = panel_names.HOME_PANEL === topPanel?.name;
|
|
const isPausedChanged = previousPanelInfo.current?.isPaused !== panelInfo?.isPaused;
|
|
|
|
if (isOnTop && panelInfo?.isPaused !== undefined && isPausedChanged) {
|
|
// 상태 변경 시에만 디버깅 로그 출력
|
|
dlog('🔍 [PlayerPanel] PanelInfo isPaused changed', {
|
|
previousIsPaused: previousPanelInfo.current?.isPaused,
|
|
currentIsPaused: panelInfo.isPaused,
|
|
isOnTop,
|
|
videoPlayerExists: !!videoPlayer.current,
|
|
currentPlayingUrl,
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
dlog('🎮 [PlayerPanel] PanelInfo isPaused changed', {
|
|
previousIsPaused: previousPanelInfo.current?.isPaused,
|
|
currentIsPaused: panelInfo.isPaused,
|
|
videoPlayerExists: !!videoPlayer.current,
|
|
currentPlayingUrl,
|
|
shptmBanrTpNm: panelInfo?.shptmBanrTpNm,
|
|
showId: panelInfo?.showId,
|
|
timestamp: new Date().toISOString(),
|
|
source: 'useEffect panelInfo.isPaused',
|
|
});
|
|
|
|
if (videoPlayer.current) {
|
|
if (panelInfo.isPaused === true) {
|
|
dlog('🔴 [PlayerPanel] Calling VideoPlayer.pause() due to PanelInfo change');
|
|
videoPlayer.current.pause();
|
|
} else if (panelInfo.isPaused === false) {
|
|
dlog('🟢 [PlayerPanel] Calling VideoPlayer.play() due to PanelInfo change');
|
|
videoPlayer.current.play();
|
|
}
|
|
}
|
|
}
|
|
|
|
previousPanelInfo.current = panelInfo;
|
|
}, [panelInfo?.isPaused, topPanel?.name, currentPlayingUrl]);
|
|
|
|
// VideoPlayer 인스턴스 및 소스 변경 모니터링 (중요 변화만)
|
|
useEffect(() => {
|
|
const isOnTop = panel_names.HOME_PANEL === topPanel?.name;
|
|
const isDetailPanelOnTop = panel_names.DETAIL_PANEL === topPanel?.name;
|
|
const isVideoSourceChanged = previousVideoSource.current !== currentPlayingUrl;
|
|
const isOnTopChanged = previousIsOnTop.current !== isOnTop;
|
|
const isDetailPanelOnTopChanged =
|
|
previousIsOnTop.current === false && isDetailPanelOnTop === true;
|
|
const videoPlayerJustCreated = previousVideoSource.current === null && videoPlayer.current;
|
|
|
|
if (
|
|
(isVideoSourceChanged ||
|
|
isOnTopChanged ||
|
|
isDetailPanelOnTopChanged ||
|
|
videoPlayerJustCreated) &&
|
|
videoPlayer.current
|
|
) {
|
|
const changeReason = isDetailPanelOnTopChanged
|
|
? 'DetailPanel opened'
|
|
: isVideoSourceChanged
|
|
? 'Video source changed'
|
|
: isOnTopChanged
|
|
? 'Top panel changed'
|
|
: 'VideoPlayer created';
|
|
|
|
dlog('🎬 [PlayerPanel] VideoPlayer state change', {
|
|
hasVideoPlayer: !!videoPlayer.current,
|
|
currentPlayingUrl,
|
|
previousVideoSource: previousVideoSource.current,
|
|
topPanelName: topPanel?.name,
|
|
isOnTop,
|
|
isDetailPanelOnTop,
|
|
isVideoSourceChanged,
|
|
isOnTopChanged,
|
|
isDetailPanelOnTopChanged,
|
|
videoPlayerJustCreated,
|
|
changeReason,
|
|
timestamp: new Date().toISOString(),
|
|
source: 'useEffect videoPlayer.current',
|
|
});
|
|
|
|
// VideoPlayer 상태 확인 (소스 변경이나 PlayerPanel 생성 시에만)
|
|
if (isVideoSourceChanged || videoPlayerJustCreated || isDetailPanelOnTopChanged) {
|
|
const mediaState = videoPlayer.current.getMediaState?.();
|
|
if (mediaState) {
|
|
dlog('📊 [PlayerPanel] VideoPlayer current state', {
|
|
mediaState,
|
|
videoPlayState,
|
|
changeReason,
|
|
timestamp: new Date().toISOString(),
|
|
source: 'useEffect getMediaState',
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
previousVideoSource.current = currentPlayingUrl;
|
|
previousIsOnTop.current = isOnTop || isDetailPanelOnTop; // 현재 최상위 패널 상태 저장
|
|
}, [videoPlayer.current, currentPlayingUrl, topPanel?.name]);
|
|
|
|
// Modal 상태 변화 감지 (true → false, false → true)
|
|
useEffect(() => {
|
|
const currentModalState = panelInfo?.modal;
|
|
const prevModalState = modalPrevRef.current;
|
|
|
|
dlog('[PlayerPanel] 🔍 Modal 상태 체크:', {
|
|
prevModalState,
|
|
currentModalState,
|
|
shouldExecuteTrueToFalse: prevModalState === true && currentModalState === false,
|
|
shouldExecuteFalseToTrue: prevModalState === false && currentModalState === true,
|
|
});
|
|
|
|
// true → false 변화 감지
|
|
if (prevModalState === true && currentModalState === false) {
|
|
dlog('[PlayerPanel] ▶️ Modal 상태 변화: true → false (전체화면 모드로 복귀)');
|
|
dlog(
|
|
'[PlayerPanel] 🎯 포커스 복원 준비 - lastFocusedTargetId:',
|
|
panelInfo?.lastFocusedTargetId
|
|
);
|
|
setIsModalClosed(true); // 모달이 닫혔음을 표시
|
|
|
|
// DetailPanel에서 복귀 시 포커스 복원
|
|
const lastFocusedTargetId = panelInfo?.lastFocusedTargetId;
|
|
|
|
if (lastFocusedTargetId) {
|
|
// ShopNowContents가 렌더링될 때까지 대기 후 포커스 복원
|
|
setTimeout(() => {
|
|
dlog('[PlayerPanel] 🔍 800ms 후 포커스 복원 시도:', lastFocusedTargetId);
|
|
Spotlight.focus(lastFocusedTargetId);
|
|
}, 800);
|
|
}
|
|
}
|
|
|
|
// false → true 변화 감지 (모달이 열림)
|
|
if (prevModalState === false && currentModalState === true) {
|
|
dlog('[PlayerPanel] 📱 Modal 상태 변화: false → true (모달 열림)');
|
|
setIsModalClosed(false); // 모달이 열렸음을 표시
|
|
}
|
|
|
|
modalPrevRef.current = currentModalState; // 현재 modal 상태를 ref에 저장
|
|
}, [panelInfo?.modal, panelInfo?.lastFocusedTargetId]);
|
|
|
|
// creating live log params
|
|
useEffect(() => {
|
|
if (currentLiveShowInfo && Object.keys(currentLiveShowInfo).length > 0) {
|
|
if (currentLiveShowInfo.showId !== panelInfo?.showId) {
|
|
dispatch(
|
|
updatePanel({
|
|
name: panel_names.PLAYER_PANEL,
|
|
panelInfo: {
|
|
chanId: currentLiveShowInfo.chanId,
|
|
modalContainerId: panelInfo?.modalContainerId,
|
|
patnrId: currentLiveShowInfo.patnrId,
|
|
showId: currentLiveShowInfo.showId,
|
|
showUrl: currentLiveShowInfo.showUrl,
|
|
},
|
|
})
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
let logTemplateNo;
|
|
|
|
if (isOnTop && panelInfo?.modal) {
|
|
logTemplateNo = getLogTpNo('LIVE', nowMenu);
|
|
setLogStatus((status) => ({ ...status, isModalLiveLogReady: true }));
|
|
}
|
|
//
|
|
else if (isOnTop && !panelInfo?.modal) {
|
|
logTemplateNo = Config.LOG_TP_NO.LIVE.FULL;
|
|
setLogStatus((status) => ({ ...status, isFullLiveLogReady: true }));
|
|
}
|
|
//
|
|
else if (!isOnTop && !panelInfo?.modal) {
|
|
logTemplateNo = Config.LOG_TP_NO.LIVE.ITEM_DETAIL;
|
|
setLogStatus((status) => ({ ...status, isDetailLiveLogReady: true }));
|
|
}
|
|
liveLogParamsRef.current = {
|
|
entryMenu: entryMenuRef.current,
|
|
lgCatCd: currentLiveShowInfo.catCd ?? '',
|
|
lgCatNm: currentLiveShowInfo.catNm ?? '',
|
|
linkTpCd: panelInfo?.linkTpCd ?? '',
|
|
logTpNo: logTemplateNo,
|
|
nowMenu: nowMenuRef.current,
|
|
patncNm: currentLiveShowInfo.patncNm,
|
|
patnrId: currentLiveShowInfo.patnrId,
|
|
showId: currentLiveShowInfo.showId,
|
|
showNm: currentLiveShowInfo.showNm,
|
|
vdoTpNm: currentLiveShowInfo.vtctpYn
|
|
? currentLiveShowInfo.vtctpYn === 'Y'
|
|
? 'Vertical'
|
|
: 'Horizontal'
|
|
: '',
|
|
};
|
|
}
|
|
}, [
|
|
currentLiveShowInfo,
|
|
isOnTop,
|
|
nowMenu,
|
|
panelInfo?.linkTpCd,
|
|
panelInfo?.modal,
|
|
panelInfo?.modalContainerId,
|
|
panelInfo?.showId,
|
|
setLogStatus,
|
|
]);
|
|
|
|
// send live log
|
|
useEffect(() => {
|
|
if (broadcast && Object.keys(broadcast).length === 0) {
|
|
// case: live, modal
|
|
if (
|
|
logStatus.isModalLiveLogReady &&
|
|
isOnTop &&
|
|
panelInfo?.modal &&
|
|
liveLogParamsRef.current?.showId === panelInfo?.showId
|
|
) {
|
|
let watchStrtDt = formatGMTString(new Date());
|
|
|
|
watchIntervalLive.current = setInterval(() => {
|
|
let watchEndDt = formatGMTString(new Date());
|
|
let watchRecord = {
|
|
...liveLogParamsRef.current,
|
|
watchStrtDt,
|
|
watchEndDt,
|
|
};
|
|
dispatch(changeLocalSettings({ watchRecord }));
|
|
}, 10000);
|
|
|
|
return () => {
|
|
setLogStatus((status) => ({
|
|
...status,
|
|
isModalLiveLogReady: false,
|
|
}));
|
|
clearInterval(watchIntervalLive.current);
|
|
dispatch(
|
|
sendLogLive({ ...liveLogParamsRef.current, watchStrtDt }, () =>
|
|
dispatch(changeLocalSettings({ watchRecord: {} }))
|
|
)
|
|
);
|
|
};
|
|
}
|
|
|
|
// case: live, full
|
|
if (
|
|
logStatus.isFullLiveLogReady &&
|
|
isOnTop &&
|
|
!panelInfo?.modal &&
|
|
liveLogParamsRef.current?.showId === panelInfo?.showId
|
|
) {
|
|
let watchStrtDt = formatGMTString(new Date());
|
|
|
|
watchIntervalLive.current = setInterval(() => {
|
|
let watchEndDt = formatGMTString(new Date());
|
|
let watchRecord = {
|
|
...liveLogParamsRef.current,
|
|
watchStrtDt,
|
|
watchEndDt,
|
|
};
|
|
dispatch(changeLocalSettings({ watchRecord }));
|
|
}, 10000);
|
|
|
|
return () => {
|
|
setLogStatus((status) => ({
|
|
...status,
|
|
isFullLiveLogReady: false,
|
|
}));
|
|
clearInterval(watchIntervalLive.current);
|
|
dispatch(
|
|
sendLogLive({ ...liveLogParamsRef.current, watchStrtDt }, () =>
|
|
dispatch(changeLocalSettings({ watchRecord: {} }))
|
|
)
|
|
);
|
|
};
|
|
}
|
|
|
|
// case: live, item detail
|
|
if (
|
|
logStatus.isDetailLiveLogReady &&
|
|
!isOnTop &&
|
|
!panelInfo?.modal &&
|
|
liveLogParamsRef.current?.showId === panelInfo?.showId
|
|
) {
|
|
let watchStrtDt = formatGMTString(new Date());
|
|
|
|
watchIntervalLive.current = setInterval(() => {
|
|
let watchEndDt = formatGMTString(new Date());
|
|
let watchRecord = {
|
|
...liveLogParamsRef.current,
|
|
watchStrtDt,
|
|
watchEndDt,
|
|
};
|
|
dispatch(changeLocalSettings({ watchRecord }));
|
|
}, 10000);
|
|
|
|
return () => {
|
|
setLogStatus((status) => ({
|
|
...status,
|
|
isDetailLiveLogReady: false,
|
|
}));
|
|
clearInterval(watchIntervalLive.current);
|
|
dispatch(
|
|
sendLogLive({ ...liveLogParamsRef.current, watchStrtDt }, () =>
|
|
dispatch(changeLocalSettings({ watchRecord: {} }))
|
|
)
|
|
);
|
|
};
|
|
}
|
|
}
|
|
}, [
|
|
broadcast,
|
|
isOnTop,
|
|
logStatus.isDetailLiveLogReady,
|
|
logStatus.isFullLiveLogReady,
|
|
logStatus.isModalLiveLogReady,
|
|
panelInfo?.modal,
|
|
panelInfo?.showId,
|
|
setLogStatus,
|
|
]);
|
|
|
|
// creating VOD log params
|
|
useEffect(() => {
|
|
if (currentVODShowInfo && Object.keys(currentVODShowInfo).length > 0 && !isVODPaused) {
|
|
let logTemplateNo;
|
|
|
|
if (isOnTop && panelInfo?.modal) {
|
|
logTemplateNo = getLogTpNo('VOD', nowMenu);
|
|
setLogStatus((status) => ({ ...status, isModalVodLogReady: true }));
|
|
}
|
|
//
|
|
else if (isOnTop && !panelInfo?.modal) {
|
|
logTemplateNo = Config.LOG_TP_NO.VOD.FULL_VOD;
|
|
setLogStatus((status) => ({ ...status, isFullVodLogReady: true }));
|
|
}
|
|
//
|
|
else if (!isOnTop && !panelInfo?.modal) {
|
|
logTemplateNo = Config.LOG_TP_NO.VOD.ITEM_DETAIL_VOD;
|
|
setLogStatus((status) => ({ ...status, isDetailVodLogReady: true }));
|
|
}
|
|
|
|
vodLogParamsRef.current = {
|
|
entryMenu: entryMenuRef.current,
|
|
lgCatCd: currentVODShowInfo?.showCatCd ?? '',
|
|
lgCatNm: currentVODShowInfo?.showCatNm ?? '',
|
|
logTpNo: logTemplateNo,
|
|
linkTpCd: panelInfo?.linkTpCd ?? '',
|
|
nowMenu: nowMenuRef.current,
|
|
patncNm: currentVODShowInfo?.patncNm,
|
|
patnrId: currentVODShowInfo?.patnrId,
|
|
showId: currentVODShowInfo?.showId,
|
|
showNm: currentVODShowInfo?.showNm,
|
|
vdoTpNm: currentVODShowInfo?.vtctpYn
|
|
? currentVODShowInfo.vtctpYn === 'Y'
|
|
? 'Vertical'
|
|
: 'Horizontal'
|
|
: '',
|
|
};
|
|
}
|
|
}, [currentVODShowInfo, isOnTop, isVODPaused, nowMenu, panelInfo?.linkTpCd, panelInfo?.modal]);
|
|
|
|
// send VOD log
|
|
useEffect(() => {
|
|
if (broadcast && Object.keys(broadcast).length === 0 && !isVODPaused) {
|
|
// case: VOD, modal
|
|
if (
|
|
logStatus.isModalVodLogReady &&
|
|
isOnTop &&
|
|
panelInfo?.modal &&
|
|
vodLogParamsRef.current?.showId === panelInfo?.showId
|
|
) {
|
|
let watchStrtDt = formatGMTString(new Date());
|
|
|
|
watchIntervalVod.current = setInterval(() => {
|
|
let watchEndDt = formatGMTString(new Date());
|
|
let watchRecord = {
|
|
...vodLogParamsRef.current,
|
|
watchStrtDt,
|
|
watchEndDt,
|
|
};
|
|
dispatch(changeLocalSettings({ watchRecord }));
|
|
}, 10000);
|
|
|
|
return () => {
|
|
setLogStatus((status) => ({
|
|
...status,
|
|
isModalVodLogReady: false,
|
|
}));
|
|
clearInterval(watchIntervalVod.current);
|
|
dispatch(
|
|
sendLogVOD({ ...vodLogParamsRef.current, watchStrtDt }, () =>
|
|
dispatch(changeLocalSettings({ watchRecord: {} }))
|
|
)
|
|
);
|
|
};
|
|
}
|
|
|
|
// case: VOD, full
|
|
if (
|
|
logStatus.isFullVodLogReady &&
|
|
isOnTop &&
|
|
!panelInfo?.modal &&
|
|
vodLogParamsRef.current?.showId === panelInfo?.showId
|
|
) {
|
|
let watchStrtDt = formatGMTString(new Date());
|
|
|
|
watchIntervalVod.current = setInterval(() => {
|
|
let watchEndDt = formatGMTString(new Date());
|
|
let watchRecord = {
|
|
...vodLogParamsRef.current,
|
|
watchStrtDt,
|
|
watchEndDt,
|
|
};
|
|
dispatch(changeLocalSettings({ watchRecord }));
|
|
}, 10000);
|
|
|
|
return () => {
|
|
setLogStatus((status) => ({
|
|
...status,
|
|
isFullVodLogReady: false,
|
|
}));
|
|
clearInterval(watchIntervalVod.current);
|
|
dispatch(
|
|
sendLogVOD({ ...vodLogParamsRef.current, watchStrtDt }, () =>
|
|
dispatch(changeLocalSettings({ watchRecord: {} }))
|
|
)
|
|
);
|
|
};
|
|
}
|
|
|
|
// case: VOD, item detail
|
|
if (
|
|
logStatus.isDetailVodLogReady &&
|
|
!isOnTop &&
|
|
!panelInfo?.modal &&
|
|
vodLogParamsRef.current?.showId === panelInfo?.showId
|
|
) {
|
|
let watchStrtDt = formatGMTString(new Date());
|
|
|
|
watchIntervalVod.current = setInterval(() => {
|
|
let watchEndDt = formatGMTString(new Date());
|
|
let watchRecord = {
|
|
...vodLogParamsRef.current,
|
|
watchStrtDt,
|
|
watchEndDt,
|
|
};
|
|
dispatch(changeLocalSettings({ watchRecord }));
|
|
}, 10000);
|
|
|
|
return () => {
|
|
setLogStatus((status) => ({
|
|
...status,
|
|
isDetailVodLogReady: false,
|
|
}));
|
|
clearInterval(watchIntervalVod.current);
|
|
dispatch(
|
|
sendLogVOD({ ...vodLogParamsRef.current, watchStrtDt }, () =>
|
|
dispatch(changeLocalSettings({ watchRecord: {} }))
|
|
)
|
|
);
|
|
};
|
|
}
|
|
}
|
|
}, [
|
|
broadcast,
|
|
isOnTop,
|
|
isVODPaused,
|
|
logStatus.isDetailVodLogReady,
|
|
logStatus.isFullVodLogReady,
|
|
logStatus.isModalVodLogReady,
|
|
panelInfo?.modal,
|
|
panelInfo?.showId,
|
|
setLogStatus,
|
|
]);
|
|
|
|
// creating media log params
|
|
useEffect(() => {
|
|
if (panelInfo?.shptmBanrTpNm === 'MEDIA' && isOnTop && !isVODPaused) {
|
|
let logTemplateNo;
|
|
|
|
if (panelInfo?.modal) {
|
|
logTemplateNo = getLogTpNo('MEDIA', nowMenu);
|
|
setLogStatus((status) => ({ ...status, isModalMediaLogReady: true }));
|
|
}
|
|
//
|
|
else if (!panelInfo?.modal) {
|
|
logTemplateNo = Config.LOG_TP_NO.VOD.FULL_MEDIA;
|
|
setLogStatus((status) => ({ ...status, isFullMediaLogReady: true }));
|
|
}
|
|
|
|
mediaLogParamsRef.current = {
|
|
entryMenu: entryMenuRef.current,
|
|
lgCatCd: panelInfo?.lgCatCd ?? '',
|
|
lgCatNm: panelInfo?.lgCatNm ?? '',
|
|
logTpNo: logTemplateNo,
|
|
linkTpCd: panelInfo?.linkTpCd ?? '',
|
|
nowMenu: nowMenuRef.current,
|
|
patncNm: panelInfo?.patncNm ?? '',
|
|
patnrId: panelInfo?.patnrId ?? '',
|
|
showId: panelInfo?.prdtId ?? panelInfo?.showId,
|
|
showNm: panelInfo?.prdtNm ?? panelInfo?.showNm,
|
|
vdoTpNm: 'Horizontal',
|
|
};
|
|
}
|
|
}, [
|
|
isOnTop,
|
|
isVODPaused,
|
|
nowMenu,
|
|
panelInfo?.lgCatCd,
|
|
panelInfo?.lgCatNm,
|
|
panelInfo?.linkTpCd,
|
|
panelInfo?.modal,
|
|
panelInfo?.patncNm,
|
|
panelInfo?.patnrId,
|
|
panelInfo?.prdtId,
|
|
panelInfo?.prdtNm,
|
|
panelInfo?.showId,
|
|
panelInfo?.showNm,
|
|
panelInfo?.shptmBanrTpNm,
|
|
setLogStatus,
|
|
]);
|
|
|
|
// send log media
|
|
useEffect(() => {
|
|
if (broadcast && Object.keys(broadcast).length === 0 && !isVODPaused) {
|
|
// case: media, modal
|
|
if (logStatus.isModalMediaLogReady && panelInfo?.modal) {
|
|
let watchStrtDt = formatGMTString(new Date());
|
|
|
|
watchIntervalMedia.current = setInterval(() => {
|
|
let watchEndDt = formatGMTString(new Date());
|
|
let watchRecord = {
|
|
...mediaLogParamsRef.current,
|
|
watchStrtDt,
|
|
watchEndDt,
|
|
};
|
|
dispatch(changeLocalSettings({ watchRecord }));
|
|
}, 10000);
|
|
|
|
return () => {
|
|
setLogStatus((status) => ({
|
|
...status,
|
|
isModalMediaLogReady: false,
|
|
}));
|
|
clearInterval(watchIntervalMedia.current);
|
|
dispatch(
|
|
sendLogVOD({ ...mediaLogParamsRef.current, watchStrtDt }, () =>
|
|
dispatch(changeLocalSettings({ watchRecord: {} }))
|
|
)
|
|
);
|
|
};
|
|
}
|
|
}
|
|
|
|
// case: media, full
|
|
if (logStatus.isFullMediaLogReady && !panelInfo?.modal) {
|
|
let watchStrtDt = formatGMTString(new Date());
|
|
|
|
watchIntervalMedia.current = setInterval(() => {
|
|
let watchEndDt = formatGMTString(new Date());
|
|
let watchRecord = {
|
|
...mediaLogParamsRef.current,
|
|
watchStrtDt,
|
|
watchEndDt,
|
|
};
|
|
dispatch(changeLocalSettings({ watchRecord }));
|
|
}, 10000);
|
|
|
|
return () => {
|
|
setLogStatus((status) => ({
|
|
...status,
|
|
isFullMediaLogReady: false,
|
|
}));
|
|
clearInterval(watchIntervalMedia.current);
|
|
dispatch(
|
|
sendLogVOD({ ...mediaLogParamsRef.current, watchStrtDt }, () =>
|
|
dispatch(changeLocalSettings({ watchRecord: {} }))
|
|
)
|
|
);
|
|
};
|
|
}
|
|
}, [
|
|
broadcast,
|
|
isVODPaused,
|
|
logStatus.isFullMediaLogReady,
|
|
logStatus.isModalMediaLogReady,
|
|
panelInfo?.modal,
|
|
setLogStatus,
|
|
]);
|
|
|
|
const videoVerticalVisible = useMemo(() => {
|
|
if (playListInfo && playListInfo[selectedIndex]?.vtctpYn === 'Y') {
|
|
return true;
|
|
}
|
|
return false;
|
|
}, [playListInfo, selectedIndex]);
|
|
|
|
const handleItemFocus = useCallback(
|
|
(menu) => {
|
|
dispatch(sendLogGNB(menu));
|
|
|
|
if (!videoVerticalVisible) {
|
|
resetTimer(REGULAR_TIMEOUT);
|
|
}
|
|
},
|
|
[dispatch, resetTimer, videoVerticalVisible]
|
|
);
|
|
|
|
const onClickBack = useCallback(
|
|
(ev, isEnd) => {
|
|
//modal로부터 Full 전환된 경우 다시 preview 모드로 돌아감.
|
|
|
|
// TabContainer(v1)만: Side Contents가 보이는 경우 먼저 숨기고 return
|
|
if (
|
|
tabContainerVersion === 1 &&
|
|
sideContentsVisible &&
|
|
!videoVerticalVisible &&
|
|
panelInfo.shptmBanrTpNm !== 'MEDIA'
|
|
) {
|
|
setSideContentsVisible(false);
|
|
ev?.stopPropagation();
|
|
// ev?.preventDefault();
|
|
return;
|
|
}
|
|
if (panelInfo.modalContainerId && !panelInfo.modal) {
|
|
dispatch(
|
|
startVideoPlayer({
|
|
...panelInfo,
|
|
modal: true,
|
|
modalClassName: '',
|
|
})
|
|
);
|
|
videoPlayer.current?.hideControls();
|
|
setSelectedIndex(backupInitialIndex);
|
|
if (panelInfo.shptmBanrTpNm === 'MEDIA') {
|
|
dispatch(
|
|
updatePanel({
|
|
name: panel_names.DETAIL_PANEL,
|
|
panelInfo: {
|
|
launchedFromPlayer: false,
|
|
},
|
|
})
|
|
);
|
|
}
|
|
ev?.stopPropagation();
|
|
// ev?.preventDefault();
|
|
return;
|
|
}
|
|
|
|
if (!panelInfo.modal) {
|
|
dispatch(PanelActions.popPanel());
|
|
dispatch(changeAppStatus({ cursorVisible: false }));
|
|
|
|
//딮링크로 플레이어 진입 후 이전버튼 클릭시
|
|
if (panels.length === 1) {
|
|
setTimeout(() => {
|
|
Spotlight.focus(SpotlightIds.HOME_TBODY);
|
|
});
|
|
}
|
|
ev?.stopPropagation();
|
|
// ev?.preventDefault();
|
|
return;
|
|
}
|
|
},
|
|
[
|
|
dispatch,
|
|
panelInfo,
|
|
videoPlayer,
|
|
sideContentsVisible,
|
|
videoVerticalVisible,
|
|
backupInitialIndex,
|
|
panels,
|
|
tabContainerVersion,
|
|
]
|
|
);
|
|
|
|
useEffect(() => {
|
|
//todo if(modal)
|
|
return () => {
|
|
// 패널이 2개 존재할때만 popPanel 진행
|
|
if (panelInfo.modal && !isOnTop) {
|
|
dispatch(PanelActions.popPanel());
|
|
} else {
|
|
Spotlight.focus('tbody');
|
|
}
|
|
};
|
|
}, [panelInfo?.modal, isOnTop]);
|
|
|
|
useEffect(() => {
|
|
if (showNowInfos && panelInfo.shptmBanrTpNm === 'LIVE') {
|
|
const period = showNowInfos.period !== undefined ? showNowInfos.period : 30;
|
|
const periodInMilliseconds = period * 1000;
|
|
|
|
const timer = setTimeout(() => {
|
|
dispatch(
|
|
getMainLiveShowNowProduct({
|
|
patnrId: panelInfo.patnrId ? panelInfo.patnrId : playListInfo[selectedIndex].patnrId,
|
|
showId: panelInfo.showId ? panelInfo.showId : playListInfo[selectedIndex].showId,
|
|
lstChgDt: showNowInfos.lstChgDt,
|
|
})
|
|
);
|
|
}, periodInMilliseconds);
|
|
|
|
return () => {
|
|
clearTimeout(timer);
|
|
};
|
|
}
|
|
}, [showNowInfos, panelInfo]);
|
|
|
|
const videoItemFocused = useCallback(() => {
|
|
// 아이템클릭 진입시 포커스
|
|
let hasProperSpot = false;
|
|
let targetId;
|
|
if (!isInitialFocusOccurred) {
|
|
targetId = panelInfo.targetId;
|
|
|
|
initialFocusTimeoutJob.current.start(() => {
|
|
const initialFocusTarget = findSelector(`[data-spotlight-id="${targetId}"]`);
|
|
|
|
if (initialFocusTarget) {
|
|
hasProperSpot = true;
|
|
Spotlight.focus(initialFocusTarget);
|
|
setIsInitialFocusOccurred(true);
|
|
|
|
return;
|
|
}
|
|
});
|
|
}
|
|
}, [isInitialFocusOccurred, panelInfo.targetId, panelInfo.modal, videoVerticalVisible]);
|
|
|
|
const videoInitialFocused = useCallback(() => {
|
|
if (panelInfo.isUpdatedByClick || !isOnTop) {
|
|
return;
|
|
}
|
|
setContainerLastFocusedElement(null, ['playVideoShopNowBox']);
|
|
|
|
// 세로형모드 포커스
|
|
if (videoVerticalVisible) {
|
|
Spotlight.focus('tab-0');
|
|
return;
|
|
}
|
|
|
|
// 화살표버튼 포커스
|
|
const current = Spotlight.getCurrent();
|
|
let hasProperSpot = false;
|
|
if (current) {
|
|
const spotId = current.getAttribute('data-spotlight-id');
|
|
if (spotId && spotId.indexOf('tabChannel-video') >= 0) {
|
|
hasProperSpot = true;
|
|
}
|
|
}
|
|
|
|
if (!panelInfo.modal && !videoVerticalVisible && !hasProperSpot) {
|
|
if (tabContainerVersion === 1) {
|
|
Spotlight.focus(SpotlightIds.PLAYER_TAB_BUTTON);
|
|
} else if (tabContainerVersion === 2) {
|
|
Spotlight.focus('below-tab-live-channel-button');
|
|
}
|
|
return;
|
|
}
|
|
//비디오 진입시 포커스
|
|
if (panelInfo.isIndicatorByClick && shopNowInfo?.length > 0) {
|
|
Spotlight.focus('playVideoShopNowBox');
|
|
return;
|
|
}
|
|
}, [
|
|
shopNowInfo,
|
|
videoVerticalVisible,
|
|
isOnTop,
|
|
panelInfo.modal,
|
|
panelInfo.isUpdatedByClick,
|
|
panelInfo.isIndicatorByClick,
|
|
panelInfo.shptmBanrTpNm,
|
|
tabContainerVersion,
|
|
]);
|
|
|
|
// 최상단 패널 정보 (여러 useMemo에서 공통으로 사용)
|
|
const topPanel = useMemo(() => {
|
|
return panels[panels.length - 1];
|
|
}, [panels]);
|
|
|
|
// 최상단 패널이 DetailPanel이고 PlayerPanel에서 진입했는지 확인
|
|
const isTopPanelDetailFromPlayer = useMemo(() => {
|
|
const result =
|
|
topPanel?.name === panel_names.DETAIL_PANEL &&
|
|
topPanel?.panelInfo?.launchedFromPlayer === true;
|
|
|
|
// 🔍 DetailPanel 상태 변화 로깅
|
|
if (result) {
|
|
dlog('🎬 [PlayerPanel] DetailPanel is now on top (from Player)', {
|
|
topPanelName: topPanel?.name,
|
|
launchedFromPlayer: topPanel?.panelInfo?.launchedFromPlayer,
|
|
modalPlayerPanelExists: panels.some(
|
|
(p) => p.name === panel_names.PLAYER_PANEL && p.panelInfo?.modal
|
|
),
|
|
currentVideoState: {
|
|
isPlaying: videoPlayState?.isPlaying,
|
|
isPaused: videoPlayState?.isPaused,
|
|
currentTime: videoPlayState?.currentTime,
|
|
},
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
}
|
|
|
|
return result;
|
|
}, [topPanel, panels, videoPlayState]);
|
|
|
|
// 🔍 PlayerPanel이 밑에 깔렸을 때 자신의 상태를 업데이트하는 useEffect
|
|
useEffect(() => {
|
|
const isDetailPanelOnTop = topPanel?.name === panel_names.DETAIL_PANEL;
|
|
const isModalPlayerPanel = panelInfo?.modal === true;
|
|
const isCurrentPanelOnTop = topPanel?.name === panel_names.PLAYER_PANEL;
|
|
|
|
// PlayerPanel이 modal이고 밑에 깔렸을 때
|
|
if (isModalPlayerPanel && !isCurrentPanelOnTop && isDetailPanelOnTop) {
|
|
dlog('🔴 [PlayerPanel] Self-pausing due to DetailPanel on top', {
|
|
isDetailPanelOnTop,
|
|
isModalPlayerPanel,
|
|
isCurrentPanelOnTop,
|
|
currentReduxState: videoPlayState,
|
|
needsPause: videoPlayState?.isPlaying === true || videoPlayState?.isPaused === false,
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
|
|
// PlayerPanel 자신의 상태를 일시정지로 업데이트
|
|
if (videoPlayState?.isPlaying === true || videoPlayState?.isPaused === false) {
|
|
dlog('🔄 [PlayerPanel] Dispatching self-pause to Redux');
|
|
dispatch(
|
|
updateVideoPlayState({
|
|
isPlaying: false,
|
|
isPaused: true,
|
|
currentTime: videoPlayState?.currentTime || 0,
|
|
duration: videoPlayState?.duration || 0,
|
|
playbackRate: videoPlayState?.playbackRate || 1,
|
|
source: 'PlayerPanel-self-pause',
|
|
})
|
|
);
|
|
}
|
|
}
|
|
}, [topPanel?.name, panelInfo?.modal, videoPlayState, dispatch]);
|
|
|
|
const cannotPlay = useMemo(() => {
|
|
return !isOnTop && topPanel?.name === panel_names.PLAYER_PANEL;
|
|
}, [topPanel, isOnTop]);
|
|
|
|
const getPlayer = useCallback((ref) => {
|
|
videoPlayer.current = ref;
|
|
}, []);
|
|
|
|
/** for VOD */
|
|
const addPanelInfoToPlayList = useCallback(
|
|
(featuredShowsInfos) => {
|
|
if (Array.isArray(featuredShowsInfos)) {
|
|
const showId = showDetailInfo[0]?.showId;
|
|
|
|
const index = featuredShowsInfos.findIndex((show) => show.showId === showId);
|
|
|
|
let newArray = [];
|
|
if (index > -1) {
|
|
// 인덱스를 찾은 경우 그대로
|
|
newArray = [...featuredShowsInfos];
|
|
setBackupInitialIndex(index);
|
|
setSelectedIndex(index);
|
|
} else {
|
|
// 인덱스를 찾지 못한 경우 showDetailInfo 를 제일 앞에 배치
|
|
newArray = [{ ...showDetailInfo[0] }, ...featuredShowsInfos];
|
|
setBackupInitialIndex(0);
|
|
setSelectedIndex(0);
|
|
}
|
|
setPlayListInfo(newArray);
|
|
}
|
|
},
|
|
|
|
[showDetailInfo]
|
|
);
|
|
|
|
useEffect(() => {
|
|
if (panelInfo.shptmBanrTpNm === 'LIVE') {
|
|
if (panelInfo.patnrId && panelInfo.showId) {
|
|
dispatch(
|
|
getMainCategoryShowDetail({
|
|
patnrId: panelInfo.patnrId,
|
|
showId: panelInfo.showId,
|
|
curationId: panelInfo.curationId,
|
|
})
|
|
);
|
|
}
|
|
dispatch(getMainLiveShow({ vodIncFlag: 'Y' }));
|
|
}
|
|
}, [
|
|
dispatch,
|
|
panelInfo?.curationId,
|
|
panelInfo?.lgCatCd,
|
|
panelInfo?.patnrId,
|
|
panelInfo?.showId,
|
|
panelInfo?.shptmBanrTpNm,
|
|
]);
|
|
|
|
useEffect(() => {
|
|
if (panelInfo.shptmBanrTpNm === 'VOD' && showDetailInfo && showDetailInfo.length > 0) {
|
|
// 현재 panelInfo의 showId와 showDetailInfo의 showId가 일치할 때만 처리
|
|
if (showDetailInfo[0]?.showId === panelInfo.showId) {
|
|
if (showDetailInfo[0]?.showCatCd && fullVideolgCatCd !== showDetailInfo[0]?.showCatCd) {
|
|
dispatch(
|
|
getHomeFullVideoInfo({
|
|
lgCatCd: showDetailInfo[0].showCatCd,
|
|
})
|
|
);
|
|
}
|
|
if (showDetailInfo[0].showId && showDetailInfo[0].patnrId) {
|
|
if (!featuredShowsInfos || Object.keys(featuredShowsInfos).length === 0) {
|
|
setPlayListInfo(showDetailInfo);
|
|
// VOD는 단일 비디오이므로 selectedIndex를 0으로 고정
|
|
setSelectedIndex(0);
|
|
}
|
|
setShopNowInfo(showDetailInfo[0].productInfos);
|
|
saveToLocalSettings(showDetailInfo[0].showId, showDetailInfo[0].patnrId);
|
|
}
|
|
} else {
|
|
// showId가 일치하지 않으면 이전 상태를 재활용하지 않고 초기화
|
|
dlog('[PlayerPanel] VOD showDetailInfo mismatch. Clearing playListInfo.', {
|
|
panelInfoShowId: panelInfo.showId,
|
|
showDetailInfoId: showDetailInfo[0]?.showId,
|
|
});
|
|
setPlayListInfo(null);
|
|
setSelectedIndex(null);
|
|
setShopNowInfo(null);
|
|
}
|
|
}
|
|
}, [
|
|
showDetailInfo,
|
|
panelInfo.showId,
|
|
panelInfo.shptmBanrTpNm,
|
|
fullVideolgCatCd,
|
|
featuredShowsInfos,
|
|
]);
|
|
|
|
//LIVE
|
|
useEffect(() => {
|
|
if (playListInfo && playListInfo.length > 0 && panelInfo.shptmBanrTpNm === 'LIVE') {
|
|
if (playListInfo[selectedIndex]?.patnrId) {
|
|
dispatch(
|
|
getMainLiveShowNowProduct({
|
|
patnrId: playListInfo[selectedIndex]?.patnrId,
|
|
showId: playListInfo[selectedIndex]?.showId,
|
|
})
|
|
);
|
|
}
|
|
|
|
if (playListInfo[selectedIndex]?.catCd) {
|
|
dispatch(
|
|
getHomeFullVideoInfo({
|
|
lgCatCd: playListInfo[selectedIndex]?.catCd,
|
|
})
|
|
);
|
|
}
|
|
}
|
|
}, [selectedIndex]);
|
|
|
|
useEffect(() => {
|
|
if (showDetailInfo && showDetailInfo.length > 0) {
|
|
dispatch(CLEAR_PLAYER_INFO());
|
|
|
|
if (
|
|
showDetailInfo[0]?.liveFlag === 'N' &&
|
|
showDetailInfo[0]?.chatLogFlag === 'Y' &&
|
|
panelInfo.shptmBanrTpNm === 'VOD'
|
|
) {
|
|
dispatch(getChatLog({ patnrId: panelInfo.patnrId, showId: panelInfo.showId }));
|
|
}
|
|
}
|
|
}, [showDetailInfo]);
|
|
|
|
// videoClick focused
|
|
useEffect(() => {
|
|
if (playListInfo && playListInfo.length > 0) {
|
|
if (panelInfo.targetId) {
|
|
videoItemFocused();
|
|
} else {
|
|
videoInitialFocused();
|
|
}
|
|
}
|
|
}, [playListInfo]);
|
|
|
|
//10초 후 닫힐때 TabButton 포커스
|
|
useEffect(() => {
|
|
if (playListInfo && playListInfo.length > 0) {
|
|
videoInitialFocused();
|
|
}
|
|
}, [sideContentsVisible, panelInfo.modal]);
|
|
|
|
// liveChannel initial selectedIndex
|
|
useEffect(() => {
|
|
if (panelInfo?.shptmBanrTpNm === 'LIVE' && playListInfo?.length > 0) {
|
|
const index = playListInfo.findIndex((item) => item.chanId === panelInfo.chanId);
|
|
if (index !== -1 && !isUpdate) {
|
|
setBackupInitialIndex(index);
|
|
setSelectedIndex(index);
|
|
setIsUpdate(true);
|
|
}
|
|
}
|
|
}, [panelInfo?.shptmBanrTpNm, playListInfo]);
|
|
|
|
// live subtitle Luna API
|
|
useEffect(() => {
|
|
if (currentSubtitleBlob) {
|
|
return;
|
|
} else if (isYoutube) {
|
|
if (mediaId) {
|
|
dispatch(requestLiveSubtitle({ mediaId, enable: false }));
|
|
setMediaId(null);
|
|
}
|
|
return;
|
|
//do caption action on VideoPlayer(componentDidUpdate)
|
|
} else {
|
|
if (mediaId && captionEnable && isSubtitleActive && !panelInfo?.modal) {
|
|
dispatch(requestLiveSubtitle({ mediaId, enable: true }));
|
|
} else {
|
|
if (mediaId) {
|
|
dispatch(requestLiveSubtitle({ mediaId, enable: false }));
|
|
}
|
|
}
|
|
}
|
|
}, [
|
|
mediaId,
|
|
isYoutube,
|
|
captionEnable,
|
|
isSubtitleActive,
|
|
currentSubtitleBlob,
|
|
panelInfo.modal,
|
|
panelInfo.shptmBanrTpNm,
|
|
]);
|
|
|
|
// get PlayListInfo
|
|
useEffect(() => {
|
|
if (panelInfo?.shptmBanrTpNm === 'VOD') {
|
|
if (showDetailInfo && showDetailInfo.length > 0) {
|
|
if (featuredShowsInfos && featuredShowsInfos.length > 0) {
|
|
addPanelInfoToPlayList(featuredShowsInfos);
|
|
}
|
|
}
|
|
}
|
|
}, [featuredShowsInfos]);
|
|
|
|
// get PlayListInfo
|
|
useEffect(() => {
|
|
if (!panelInfo) return;
|
|
|
|
switch (panelInfo.shptmBanrTpNm) {
|
|
case 'LIVE': {
|
|
const playlist = liveShowInfos ?? liveChannelInfos;
|
|
|
|
if (!Array.isArray(playlist)) return;
|
|
|
|
const modifiedList = [];
|
|
|
|
playlist.forEach((item) => {
|
|
if (item.showType === 'vod' && Array.isArray(item.vodInfos)) {
|
|
const mergedVodInfos = item.vodInfos.map((vod) => ({
|
|
...vod,
|
|
patnrId: item.patnrId,
|
|
patncNm: item.patncNm,
|
|
patncLogoPath: item.patncLogoPath,
|
|
showType: 'vod',
|
|
}));
|
|
|
|
modifiedList.push(...mergedVodInfos);
|
|
} else {
|
|
modifiedList.push(item);
|
|
}
|
|
});
|
|
|
|
setPlayListInfo(modifiedList);
|
|
|
|
if (showNowInfos?.prdtChgYn === 'N') {
|
|
return;
|
|
}
|
|
|
|
if (showNowInfos || showDetailInfo?.length > 0) {
|
|
const productInfos = showNowInfos
|
|
? showNowInfos.productInfos
|
|
: showDetailInfo[0]?.productInfos;
|
|
setShopNowInfo(productInfos);
|
|
}
|
|
break;
|
|
}
|
|
case 'MEDIA':
|
|
setPlayListInfo([panelInfo]);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}, [
|
|
panelInfo,
|
|
showDetailInfo,
|
|
featuredShowsInfos,
|
|
liveChannelInfos,
|
|
liveShowInfos,
|
|
showNowInfos,
|
|
dispatch,
|
|
]);
|
|
|
|
const liveTotalTime = useMemo(() => {
|
|
let liveTotalTime;
|
|
if (liveShowInfos && panelInfo?.shptmBanrTpNm === 'LIVE') {
|
|
const startDtMoment = new Date(liveShowInfos[selectedIndex]?.strtDt);
|
|
const endDtMoment = new Date(liveShowInfos[selectedIndex]?.endDt);
|
|
|
|
liveTotalTime = Math.floor((endDtMoment - startDtMoment) / 1000);
|
|
|
|
return liveTotalTime;
|
|
}
|
|
}, [liveShowInfos, selectedIndex, panelInfo.shptmBanrTpNm]);
|
|
|
|
useEffect(() => {
|
|
const handleVisibilityChange = () => {
|
|
if (
|
|
document.visibilityState === 'visible' &&
|
|
liveShowInfos &&
|
|
panelInfo?.shptmBanrTpNm === 'LIVE'
|
|
) {
|
|
const localStartDt = convertUtcToLocal(liveShowInfos[selectedIndex]?.strtDt);
|
|
|
|
const curDt = new Date();
|
|
const localStartSec = localStartDt?.getTime() / 1000;
|
|
const curSec = curDt?.getTime() / 1000;
|
|
|
|
const calculatedLiveTime = curSec - localStartSec;
|
|
if (calculatedLiveTime >= liveTotalTime) {
|
|
setCurrentLiveTimeSeconds(0);
|
|
} else {
|
|
setCurrentLiveTimeSeconds(calculatedLiveTime);
|
|
}
|
|
}
|
|
};
|
|
|
|
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
|
|
if (panelInfo.offsetHour) {
|
|
setCurrentLiveTimeSeconds(parseInt(panelInfo.offsetHour));
|
|
} else if (liveShowInfos && panelInfo?.shptmBanrTpNm === 'LIVE') {
|
|
const localStartDt = convertUtcToLocal(liveShowInfos[selectedIndex]?.strtDt);
|
|
|
|
const curDt = new Date();
|
|
const localStartSec = localStartDt?.getTime() / 1000;
|
|
const curSec = curDt?.getTime() / 1000;
|
|
|
|
const calculatedLiveTime = curSec - localStartSec;
|
|
if (calculatedLiveTime >= liveTotalTime) {
|
|
setCurrentLiveTimeSeconds(0);
|
|
} else {
|
|
setCurrentLiveTimeSeconds(calculatedLiveTime);
|
|
}
|
|
} else {
|
|
setCurrentLiveTimeSeconds(0);
|
|
}
|
|
|
|
return () => {
|
|
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
};
|
|
}, [
|
|
liveShowInfos,
|
|
selectedIndex,
|
|
panelInfo.offsetHour,
|
|
panelInfo.shptmBanrTpNm,
|
|
playListInfo,
|
|
liveTotalTime,
|
|
]);
|
|
|
|
useEffect(() => {
|
|
if (panelInfo.shptmBanrTpNm == 'LIVE' && liveTotalTime > 0) {
|
|
const interval = setInterval(() => {
|
|
setCurrentLiveTimeSeconds((prev) => {
|
|
if (prev >= liveTotalTime) {
|
|
return 1;
|
|
}
|
|
return prev + 1;
|
|
});
|
|
}, 1000);
|
|
|
|
return () => clearInterval(interval);
|
|
}
|
|
}, [liveTotalTime]);
|
|
|
|
useEffect(() => {
|
|
if (currentLiveTimeSeconds > liveTotalTime) {
|
|
setTimeout(() => {
|
|
dispatch(getMainLiveShow());
|
|
setShopNowInfo('');
|
|
dispatch(
|
|
getHomeFullVideoInfo({
|
|
lgCatCd: playListInfo[selectedIndex].showCatCd,
|
|
})
|
|
);
|
|
}, 3000);
|
|
}
|
|
}, [currentLiveTimeSeconds, liveTotalTime]);
|
|
|
|
const mediainfoHandler = useCallback(
|
|
(ev) => {
|
|
const type = ev.type;
|
|
if (type !== 'timeupdate' && type !== 'durationchange') {
|
|
dlog('mediainfoHandler....', type, ev, videoPlayer.current?.getMediaState());
|
|
}
|
|
if (ev === 'hlsError' && isNaN(Number(videoPlayer.current?.getMediaState().playbackRate))) {
|
|
dispatch(
|
|
sendBroadCast({
|
|
type: 'videoError',
|
|
moreInfo: { reason: 'hlsError' },
|
|
})
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
switch (type) {
|
|
case 'timeupdate': {
|
|
setCurrentTime(videoPlayer.current?.getMediaState()?.currentTime);
|
|
break;
|
|
}
|
|
case 'error': {
|
|
dispatch(
|
|
sendBroadCast({
|
|
type: 'videoError',
|
|
moreInfo: { reason: videoPlayer.current?.getMediaState().error },
|
|
})
|
|
);
|
|
break;
|
|
}
|
|
case 'loadeddata': {
|
|
const mediaId = videoPlayer.current?.video?.media?.mediaId;
|
|
setMediaId(mediaId);
|
|
setVideoLoaded(true);
|
|
dlog(
|
|
'[PlayerPanel] 🎬 Video Loaded - shptmBanrTpNm:',
|
|
panelInfoRef.current?.shptmBanrTpNm
|
|
);
|
|
}
|
|
}
|
|
},
|
|
[dispatch]
|
|
);
|
|
|
|
useEffect(() => {
|
|
// case: video error when the video is in fullscreen mode
|
|
if (
|
|
broadcast?.type === 'videoError' &&
|
|
isOnTop &&
|
|
!panelInfo?.modal &&
|
|
panelInfo?.modalContainerId
|
|
) {
|
|
// case: Featured Brands
|
|
if (panelInfo?.sourcePanel === panel_names.FEATURED_BRANDS_PANEL) {
|
|
dispatch(PanelActions.popPanel());
|
|
}
|
|
}
|
|
}, [
|
|
broadcast?.type,
|
|
isOnTop,
|
|
panelInfo?.modal,
|
|
panelInfo?.modalContainerId,
|
|
panelInfo?.sourcePanel,
|
|
]);
|
|
|
|
useEffect(() => {
|
|
// 복구 직후: skipModalStyleRecalculation이 true면 저장된 modalStyle 적용
|
|
if (panelInfo.skipModalStyleRecalculation && !panelInfo.shouldShrinkTo1px) {
|
|
dlog('[PlayerPanel] Condition 2.5: Using saved modalStyle from expand');
|
|
const shrinkInfo = panelInfo.playerState?.shrinkInfo;
|
|
|
|
// 저장된 modalStyle 사용 (top, left 포함)
|
|
if (shrinkInfo?.modalStyle) {
|
|
setModalStyle(shrinkInfo.modalStyle);
|
|
setModalScale(panelInfo.modalScale || shrinkInfo.modalScale);
|
|
} else {
|
|
setModalStyle(panelInfo.modalStyle);
|
|
setModalScale(panelInfo.modalScale);
|
|
}
|
|
|
|
// DOM 렌더링 후 플래그 제거
|
|
if (typeof window !== 'undefined') {
|
|
window.requestAnimationFrame(() => {
|
|
window.requestAnimationFrame(() => {
|
|
dlog('[PlayerPanel] Condition 2.5: Removing skipFlag after DOM render');
|
|
dispatch(
|
|
updatePanel({
|
|
name: panel_names.PLAYER_PANEL,
|
|
panelInfo: {
|
|
...panelInfo,
|
|
skipModalStyleRecalculation: false,
|
|
},
|
|
})
|
|
);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
if (
|
|
panelInfo.modal &&
|
|
!panelInfo.shouldShrinkTo1px &&
|
|
panelInfo.modalContainerId &&
|
|
(lastPanelAction === 'previewPush' || lastPanelAction === 'previewUpdate')
|
|
) {
|
|
dlog('[PlayerPanel] Condition 1: Calculating modalStyle from DOM', {
|
|
lastPanelAction,
|
|
});
|
|
const node = document.querySelector(`[data-spotlight-id="${panelInfo.modalContainerId}"]`);
|
|
if (node) {
|
|
const { width, height, top, left } = node.getBoundingClientRect();
|
|
const modalStyle = {
|
|
width: width + 'px',
|
|
height: height + 'px',
|
|
top: top + 'px',
|
|
left: left + 'px',
|
|
position: 'fixed',
|
|
overflow: 'visible',
|
|
};
|
|
setModalStyle(modalStyle);
|
|
let scale = 1;
|
|
if (typeof window === 'object') {
|
|
scale = width / window.innerWidth;
|
|
setModalScale(scale);
|
|
}
|
|
dispatch(
|
|
updatePanel({
|
|
name: panel_names.PLAYER_PANEL,
|
|
panelInfo: { modalStyle: modalStyle, modalScale: scale },
|
|
})
|
|
);
|
|
} else {
|
|
dlog('[PlayerPanel] Condition 1: Node not found, using saved modalStyle');
|
|
setModalStyle(panelInfo.modalStyle);
|
|
setModalScale(panelInfo.modalScale);
|
|
}
|
|
} else if (panelInfo.shouldShrinkTo1px) {
|
|
dlog('[PlayerPanel] Condition 2: Shrinking - clearing modalStyle');
|
|
// 축소 상태: 인라인 스타일 제거 (CSS만 적용)
|
|
setModalStyle({});
|
|
setModalScale(1);
|
|
} else if (isOnTop && !panelInfo.modal && videoPlayer.current) {
|
|
dlog('[PlayerPanel] Condition 3: Playing fullscreen video');
|
|
if (videoPlayer.current?.getMediaState()?.paused) {
|
|
videoPlayer.current.play();
|
|
}
|
|
|
|
if (videoPlayer.current.areControlsVisible && !videoPlayer.current.areControlsVisible()) {
|
|
videoPlayer.current.showControls();
|
|
}
|
|
}
|
|
}, [panelInfo, isOnTop, dispatch]);
|
|
|
|
const smallestOffsetHourIndex = useMemo(() => {
|
|
if (shopNowInfo) {
|
|
const filteredVideos = shopNowInfo.filter((video) => video.offsetHour >= currentTime);
|
|
const newSmallestOffsetHour = Math.min(...filteredVideos.map((video) => video.offsetHour));
|
|
|
|
const newSmallestOffsetHourIndex = shopNowInfo.findIndex(
|
|
(video) => video.offsetHour === newSmallestOffsetHour.toString()
|
|
);
|
|
|
|
if (shopNowInfo.length === 1) {
|
|
return 0;
|
|
}
|
|
if (newSmallestOffsetHourIndex >= 1) {
|
|
return newSmallestOffsetHourIndex - 1;
|
|
}
|
|
return newSmallestOffsetHourIndex;
|
|
}
|
|
}, [shopNowInfo, currentTime]);
|
|
|
|
const currentSubtitleUrl = useMemo(() => {
|
|
if (panelInfo?.shptmBanrTpNm === 'MEDIA') {
|
|
return panelInfo.subtitle;
|
|
}
|
|
return playListInfo && playListInfo[selectedIndex]?.showSubtitlUrl;
|
|
}, [playListInfo, selectedIndex, panelInfo]);
|
|
|
|
const currentPlayingUrl = useMemo(() => {
|
|
// return "https://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4";
|
|
|
|
if (broadcast.type === 'videoError') {
|
|
return null;
|
|
}
|
|
|
|
return playListInfo && playListInfo[selectedIndex]?.showUrl;
|
|
}, [playListInfo, selectedIndex, broadcast]);
|
|
|
|
const isYoutube = useMemo(() => {
|
|
if (currentPlayingUrl && currentPlayingUrl.includes('youtu')) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}, [currentPlayingUrl]);
|
|
|
|
const currentSubtitleBlob = useMemo(() => {
|
|
if (Config.DEBUG_VIDEO_SUBTITLE_TEST) {
|
|
return dummyVtt;
|
|
}
|
|
return vodSubtitleData[currentSubtitleUrl];
|
|
}, [vodSubtitleData, currentSubtitleUrl]);
|
|
|
|
const isReadyToPlay = useMemo(() => {
|
|
if (!currentPlayingUrl) {
|
|
return false;
|
|
}
|
|
if (!Config.DEBUG_VIDEO_SUBTITLE_TEST && currentSubtitleUrl && !currentSubtitleBlob) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}, [currentPlayingUrl, currentSubtitleUrl, currentSubtitleBlob, broadcast]);
|
|
|
|
const chatVisible = useMemo(() => {
|
|
if (
|
|
playListInfo &&
|
|
chatData &&
|
|
!panelInfo.modal &&
|
|
isOnTop &&
|
|
panelInfo?.shptmBanrTpNm !== 'MEDIA'
|
|
) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}, [playListInfo, chatData, panelInfo.modal, isOnTop]);
|
|
|
|
useEffect(() => {
|
|
if (currentSubtitleUrl) {
|
|
dispatch(getSubTitle({ showSubtitleUrl: currentSubtitleUrl }));
|
|
}
|
|
}, [currentSubtitleUrl]);
|
|
|
|
useEffect(() => {
|
|
setVideoLoaded(false);
|
|
}, [currentPlayingUrl]);
|
|
|
|
// 비디오가 새로 선택될 때 타이머 초기화
|
|
useEffect(() => {
|
|
if (currentPlayingUrl) {
|
|
dlog('[PlayerPanel] 🎬 비디오 선택됨 - tabIndexV2 타이머 초기화');
|
|
resetTimerTabAutoAdvance(10000);
|
|
}
|
|
}, [selectedIndex, resetTimerTabAutoAdvance]);
|
|
|
|
const handlePopupClose = useCallback(() => {
|
|
dispatch(setHidePopup());
|
|
setTimeout(() => Spotlight.focus(SpotlightIds.PLAYER_SUBTITLE_BUTTON));
|
|
}, [dispatch]);
|
|
const reactPlayerSubtitleConfig = useMemo(() => {
|
|
if (isSubtitleActive && currentSubtitleBlob) {
|
|
return {
|
|
file: {
|
|
attributes: {
|
|
crossOrigin: 'true',
|
|
},
|
|
tracks: [{ kind: 'subtitles', src: currentSubtitleBlob, default: true }],
|
|
},
|
|
youtube: YOUTUBECONFIG,
|
|
};
|
|
} else {
|
|
return {
|
|
youtube: YOUTUBECONFIG,
|
|
};
|
|
}
|
|
}, [currentSubtitleBlob, isSubtitleActive]);
|
|
|
|
const currentSideButtonStatus = useMemo(() => {
|
|
if (panelInfo?.shptmBanrTpNm !== 'MEDIA' && !panelInfo?.modal && sideContentsVisible) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}, [panelInfo, sideContentsVisible]);
|
|
|
|
const videoType = useMemo(() => {
|
|
if (currentPlayingUrl) {
|
|
if (currentPlayingUrl.toLowerCase().endsWith('.mp4')) {
|
|
return 'video/mp4';
|
|
} else if (currentPlayingUrl.toLowerCase().endsWith('.mpd')) {
|
|
return 'application/dash+xml';
|
|
} else if (currentPlayingUrl.toLowerCase().endsWith('.m3u8')) {
|
|
return 'application/mpegurl';
|
|
}
|
|
}
|
|
return 'application/mpegurl';
|
|
}, [currentPlayingUrl]);
|
|
|
|
const orderPhoneNumber = useMemo(() => {
|
|
if (panelInfo?.shptmBanrTpNm !== 'MEDIA' && showDetailInfo) {
|
|
return showDetailInfo[0]?.orderPhnNo;
|
|
} else {
|
|
return playListInfo[selectedIndex]?.orderPhnNo;
|
|
}
|
|
}, [panelInfo?.shptmBanrTpNm, showDetailInfo, playListInfo, selectedIndex]);
|
|
|
|
const videoThumbnailUrl = useMemo(() => {
|
|
let res = null;
|
|
|
|
if (panelInfo.shptmBanrTpNm === 'MEDIA') {
|
|
res = panelInfo?.thumbnailUrl;
|
|
} else if (playListInfo && playListInfo.length > 0) {
|
|
res = playListInfo[selectedIndex]?.thumbnailUrl;
|
|
} else if (!res) {
|
|
res = showDetailInfo[0]?.thumbnailUrl;
|
|
}
|
|
|
|
return res;
|
|
}, [
|
|
showDetailInfo,
|
|
playListInfo,
|
|
selectedIndex,
|
|
panelInfo.thumbnailUrl,
|
|
panelInfo.shptmBanrTpNm,
|
|
]);
|
|
|
|
const saveToLocalSettings = useCallback(
|
|
(showId, patnrId) => {
|
|
let recentItems = [];
|
|
if (localRecentItems) {
|
|
recentItems = [...localRecentItems];
|
|
}
|
|
|
|
const currentDate = new Date();
|
|
|
|
const formattedDate = `${currentDate.getMonth() + 1}/${currentDate.getDate()}`;
|
|
|
|
const existingProductIndex = recentItems.findIndex((item) => {
|
|
if (item.showId) return item.showId === showId;
|
|
});
|
|
|
|
if (existingProductIndex !== -1) {
|
|
recentItems.splice(existingProductIndex, 1);
|
|
}
|
|
|
|
recentItems.push({
|
|
patnrId: patnrId,
|
|
showId: showId,
|
|
date: formattedDate,
|
|
expireTime: currentDate.getTime() + 1000 * 60 * 60 * 24 * 14,
|
|
cntryCd: httpHeader['X-Device-Country'],
|
|
});
|
|
|
|
if (recentItems.length >= 51) {
|
|
const data = [...recentItems];
|
|
dispatch(changeLocalSettings({ recentItems: data.slice(1) }));
|
|
} else {
|
|
dispatch(changeLocalSettings({ recentItems }));
|
|
}
|
|
},
|
|
[httpHeader, localRecentItems, dispatch]
|
|
);
|
|
|
|
const handleIndicatorDownClick = useCallback(() => {
|
|
if (!initialEnter) {
|
|
setInitialEnter(true);
|
|
}
|
|
|
|
let newIndex = selectedIndex === playListInfo.length - 1 ? 0 : selectedIndex + 1;
|
|
let initialIndex = newIndex;
|
|
let attempts = 0;
|
|
|
|
while (!playListInfo[newIndex]?.showId && attempts < playListInfo.length) {
|
|
newIndex = newIndex === playListInfo.length - 1 ? 0 : newIndex + 1;
|
|
attempts++;
|
|
if (newIndex === initialIndex) break;
|
|
}
|
|
if (playListInfo[newIndex]?.showId) {
|
|
setSelectedIndex(newIndex);
|
|
if (panelInfo.shptmBanrTpNm === 'VOD') {
|
|
dispatch(
|
|
getMainCategoryShowDetail({
|
|
patnrId: playListInfo[newIndex]?.patnrId,
|
|
showId: playListInfo[newIndex]?.showId,
|
|
curationId: playListInfo[newIndex]?.curationId,
|
|
})
|
|
);
|
|
Spotlight.focus('playVideoShopNowBox');
|
|
} else {
|
|
dispatch(
|
|
updatePanel({
|
|
name: panel_names.PLAYER_PANEL,
|
|
panelInfo: {
|
|
chanId: playListInfo[newIndex].chanId,
|
|
patnrId: playListInfo[newIndex].patnrId,
|
|
showId: playListInfo[newIndex].showId,
|
|
shptmBanrTpNm: panelInfo?.shptmBanrTpNm,
|
|
isIndicatorByClick: true,
|
|
},
|
|
})
|
|
);
|
|
}
|
|
}
|
|
if (!sideContentsVisible) {
|
|
setPrevChannelIndex(selectedIndex);
|
|
}
|
|
setSideContentsVisible(true);
|
|
}, [dispatch, playListInfo, selectedIndex, sideContentsVisible, initialEnter]);
|
|
|
|
const handleIndicatorUpClick = useCallback(() => {
|
|
if (!initialEnter) {
|
|
setInitialEnter(true);
|
|
}
|
|
|
|
let newIndex = selectedIndex === 0 ? playListInfo.length - 1 : selectedIndex - 1;
|
|
let initialIndex = newIndex;
|
|
let attempts = 0;
|
|
|
|
while (!playListInfo[newIndex]?.showId && attempts < playListInfo.length) {
|
|
newIndex = newIndex === 0 ? playListInfo.length - 1 : newIndex - 1;
|
|
attempts++;
|
|
if (newIndex === initialIndex) break;
|
|
}
|
|
|
|
if (playListInfo[newIndex]?.showId) {
|
|
setSelectedIndex(newIndex);
|
|
if (panelInfo.shptmBanrTpNm === 'VOD') {
|
|
dispatch(
|
|
getMainCategoryShowDetail({
|
|
patnrId: playListInfo[newIndex]?.patnrId,
|
|
showId: playListInfo[newIndex]?.showId,
|
|
curationId: playListInfo[newIndex]?.curationId,
|
|
})
|
|
);
|
|
Spotlight.focus('playVideoShopNowBox');
|
|
} else {
|
|
dispatch(
|
|
updatePanel({
|
|
name: panel_names.PLAYER_PANEL,
|
|
panelInfo: {
|
|
chanId: playListInfo[newIndex].chanId,
|
|
patnrId: playListInfo[newIndex].patnrId,
|
|
showId: playListInfo[newIndex].showId,
|
|
shptmBanrTpNm: panelInfo?.shptmBanrTpNm,
|
|
isIndicatorByClick: true,
|
|
},
|
|
})
|
|
);
|
|
}
|
|
}
|
|
if (!sideContentsVisible) {
|
|
setPrevChannelIndex(selectedIndex);
|
|
}
|
|
setSideContentsVisible(true);
|
|
}, [dispatch, playListInfo, selectedIndex, sideContentsVisible, initialEnter]);
|
|
|
|
useEffect(() => {
|
|
if (panelInfo.shptmBanrTpNm === 'VOD' && panelInfo.patnrId && panelInfo.showId) {
|
|
//VOD의 panelInfo.showId 가 변경된 최초 한번만 호출하고, FearchedShow 항목에서 선택시 또는 상하 indicator 선택시 호출한다.
|
|
dispatch(
|
|
getMainCategoryShowDetail({
|
|
patnrId: panelInfo.patnrId,
|
|
showId: panelInfo.showId,
|
|
curationId: panelInfo.curationId,
|
|
})
|
|
);
|
|
}
|
|
}, [panelInfo.showId]);
|
|
|
|
useEffect(() => {
|
|
return () => {
|
|
dispatch(clearShopNowInfo());
|
|
dispatch(CLEAR_PLAYER_INFO());
|
|
setShopNowInfo([]);
|
|
};
|
|
}, []);
|
|
|
|
const focusBackToPanel = useCallback(() => {
|
|
// 포커스를 PlayerPanel 쪽으로 강제 이동해 YouTube iframe이 포커스를 가져가는 것을 차단
|
|
if (focusReturnRef.current) {
|
|
focusReturnRef.current.focus();
|
|
return true;
|
|
}
|
|
return false;
|
|
}, []);
|
|
|
|
const focusBackButtonOrFallback = useCallback(() => {
|
|
// 종료 시 Back 아이콘으로 포커스를 이동시키되 실패하면 센티넬로 폴백
|
|
if (Spotlight.focus('player-back-button')) {
|
|
return true;
|
|
}
|
|
return focusBackToPanel();
|
|
}, [focusBackToPanel]);
|
|
|
|
const stopExternalPlayer = useCallback(() => {
|
|
const playerInstance = videoPlayer.current;
|
|
const media = playerInstance?.video;
|
|
|
|
if (!media) {
|
|
return;
|
|
}
|
|
|
|
// ReactPlayer의 YouTube 인스턴스 대응: 종료 직후 리플레이 오버레이가 뜨지 않도록 정지/초기화 시도
|
|
if (typeof media.stopVideo === 'function') {
|
|
media.stopVideo();
|
|
}
|
|
if (typeof media.seekTo === 'function') {
|
|
media.seekTo(0);
|
|
}
|
|
if (typeof media.pause === 'function') {
|
|
media.pause();
|
|
}
|
|
}, []);
|
|
|
|
const onEnded = useCallback(
|
|
(e) => {
|
|
if (panelInfoRef.current.shptmBanrTpNm === 'MEDIA') {
|
|
dispatch(
|
|
updatePanel({
|
|
name: panel_names.DETAIL_PANEL,
|
|
panelInfo: {
|
|
launchedFromPlayer: true,
|
|
isPlayerFinished: true,
|
|
},
|
|
})
|
|
);
|
|
Spotlight.pause();
|
|
setTimeout(() => {
|
|
Spotlight.resume();
|
|
dispatch(PanelActions.popPanel());
|
|
}, VIDEO_END_ACTION_DELAY);
|
|
return;
|
|
}
|
|
if (panelInfoRef.current.shptmBanrTpNm === 'VOD') {
|
|
Spotlight.pause();
|
|
setTimeout(() => {
|
|
stopExternalPlayer();
|
|
if (panelInfoRef.current.modal) {
|
|
// 모달 모드에서는 종료 후 화면을 유지하고 Back 아이콘으로 포커스 이동
|
|
videoPlayer.current?.showControls?.();
|
|
} else {
|
|
// 전체화면 모드에서도 종료 후 즉시 닫지 않고 제어권 회수
|
|
videoPlayer.current?.showControls?.();
|
|
}
|
|
Spotlight.resume();
|
|
focusBackButtonOrFallback();
|
|
}, VIDEO_END_ACTION_DELAY);
|
|
e?.stopPropagation();
|
|
e?.preventDefault();
|
|
return;
|
|
}
|
|
},
|
|
[dispatch, focusBackButtonOrFallback, stopExternalPlayer]
|
|
);
|
|
|
|
const onKeyDown = (ev) => {
|
|
if (ev.keyCode === 34) {
|
|
handleIndicatorDownClick();
|
|
ev.stopPropagation();
|
|
ev.preventDefault();
|
|
} else if (ev.keyCode === 33) {
|
|
handleIndicatorUpClick();
|
|
ev.stopPropagation();
|
|
ev.preventDefault();
|
|
}
|
|
};
|
|
|
|
const [initialEnter, setInitialEnter] = USE_STATE('initialEnter', true);
|
|
const [initialEnterV2, setInitialEnterV2] = USE_STATE('initialEnterV2', true);
|
|
const timerId = useRef(null);
|
|
const timerIdV2 = useRef(null);
|
|
const timerIdTabAutoAdvance = useRef(null);
|
|
const prevTabIndexV2 = useRef(null);
|
|
|
|
const showSideContents = useMemo(() => {
|
|
return (
|
|
sideContentsVisible &&
|
|
playListInfo &&
|
|
panelInfo?.shptmBanrTpNm !== 'MEDIA' &&
|
|
!panelInfo?.modal &&
|
|
isOnTop
|
|
);
|
|
}, [sideContentsVisible, playListInfo, panelInfo, isOnTop]);
|
|
|
|
const showBelowContents = useMemo(() => {
|
|
return (
|
|
belowContentsVisible &&
|
|
playListInfo &&
|
|
panelInfo?.shptmBanrTpNm !== 'MEDIA' &&
|
|
!panelInfo?.modal &&
|
|
isOnTop
|
|
);
|
|
}, [belowContentsVisible, playListInfo, panelInfo, isOnTop]);
|
|
|
|
const qrCurrentItem = useMemo(() => {
|
|
if (shopNowInfo?.length && panelInfo?.shptmBanrTpNm === 'LIVE') {
|
|
return shopNowInfo[shopNowInfo.length - 1];
|
|
}
|
|
|
|
if (
|
|
shopNowInfo?.length &&
|
|
smallestOffsetHourIndex >= 0 &&
|
|
panelInfo?.shptmBanrTpNm !== 'LIVE'
|
|
) {
|
|
return shopNowInfo[smallestOffsetHourIndex];
|
|
}
|
|
|
|
if (panelInfo?.shptmBanrTpNm === 'MEDIA' && panelInfo?.qrCurrentItem) {
|
|
return panelInfo.qrCurrentItem;
|
|
}
|
|
|
|
return null;
|
|
}, [shopNowInfo, smallestOffsetHourIndex, panelInfo?.shptmBanrTpNm, panelInfo?.qrCurrentItem]);
|
|
|
|
const isShowType = useMemo(() => {
|
|
if (['VOD', 'MEDIA'].includes(panelInfo.shptmBanrTpNm)) {
|
|
return panelInfo.shptmBanrTpNm;
|
|
}
|
|
|
|
const showType = playListInfo?.[selectedIndex]?.showType;
|
|
if (showType === 'live') return panelInfo.shptmBanrTpNm;
|
|
if (showType === 'vod') return 'VOD';
|
|
|
|
return panelInfo.shptmBanrTpNm;
|
|
}, [panelInfo.shptmBanrTpNm, playListInfo, selectedIndex]);
|
|
const clearTimer = useCallback(() => {
|
|
clearTimeout(timerId.current);
|
|
timerId.current = null;
|
|
}, []);
|
|
|
|
const resetTimer = useCallback(
|
|
(timeout) => {
|
|
if (timerId.current) {
|
|
clearTimer();
|
|
}
|
|
|
|
if (initialEnter) {
|
|
setInitialEnter(false);
|
|
}
|
|
|
|
timerId.current = setTimeout(() => {
|
|
setSideContentsVisible(false);
|
|
// setBelowContentsVisible(false);
|
|
}, timeout);
|
|
},
|
|
[clearTimer, initialEnter, setInitialEnter, setSideContentsVisible]
|
|
);
|
|
|
|
const clearTimerV2 = useCallback(() => {
|
|
if (timerIdV2.current) {
|
|
dlog('[clearTimerV2] 타이머 클리어됨');
|
|
const stack = new Error().stack;
|
|
const lines = stack.split('\n').slice(1, 4).join(' → ');
|
|
dlog('[clearTimerV2] 호출 스택:', lines);
|
|
}
|
|
clearTimeout(timerIdV2.current);
|
|
timerIdV2.current = null;
|
|
}, []);
|
|
|
|
const resetTimerV2 = useCallback(
|
|
(timeout) => {
|
|
dlog('[TabContainerV2] resetTimerV2 호출', timeout);
|
|
if (timerIdV2.current) {
|
|
dlog('[TabContainerV2] 기존 타이머 클리어');
|
|
clearTimerV2();
|
|
}
|
|
|
|
if (initialEnterV2) {
|
|
dlog('[TabContainerV2] initialEnterV2 false로 변경');
|
|
setInitialEnterV2(false);
|
|
}
|
|
|
|
timerIdV2.current = setTimeout(() => {
|
|
dlog('[TabContainerV2] 타이머 실행 - belowContentsVisible false로 변경 (30초 경과)');
|
|
setBelowContentsVisible(false);
|
|
}, timeout);
|
|
},
|
|
[clearTimerV2, initialEnterV2, setInitialEnterV2, setBelowContentsVisible]
|
|
);
|
|
|
|
const clearTimerTabAutoAdvance = useCallback(() => {
|
|
clearTimeout(timerIdTabAutoAdvance.current);
|
|
timerIdTabAutoAdvance.current = null;
|
|
}, []);
|
|
|
|
const resetTimerTabAutoAdvance = useCallback(
|
|
(timeout) => {
|
|
if (timerIdTabAutoAdvance.current) {
|
|
clearTimerTabAutoAdvance();
|
|
}
|
|
|
|
timerIdTabAutoAdvance.current = setTimeout(() => {
|
|
setTabIndexV2(2);
|
|
}, timeout);
|
|
},
|
|
[clearTimerTabAutoAdvance]
|
|
);
|
|
|
|
// Redux로 오버레이 숨김
|
|
useEffect(() => {
|
|
if (shouldHideOverlays) {
|
|
dlog('[PlayerPanel] shouldHideOverlays true - 오버레이 숨김');
|
|
setSideContentsVisible(false);
|
|
dlog('[setBelowContentsVisible] Redux로 오버레이 숨김 - false로 변경');
|
|
setBelowContentsVisible(false);
|
|
|
|
if (videoPlayer.current?.hideControls) {
|
|
videoPlayer.current.hideControls();
|
|
}
|
|
|
|
// 모든 타이머 클리어
|
|
if (timerId.current) {
|
|
clearTimer();
|
|
}
|
|
if (timerIdV2.current) {
|
|
clearTimerV2();
|
|
}
|
|
if (timerIdTabAutoAdvance.current) {
|
|
clearTimerTabAutoAdvance();
|
|
}
|
|
|
|
dispatch(resetPlayerOverlays());
|
|
}
|
|
}, [shouldHideOverlays, dispatch, clearTimer, clearTimerV2, clearTimerTabAutoAdvance]);
|
|
|
|
// Redux로 오버레이 표시
|
|
useEffect(() => {
|
|
if (shouldShowOverlays) {
|
|
dlog('[PlayerPanel] shouldShowOverlays true - 오버레이 표시');
|
|
setSideContentsVisible(true);
|
|
dlog('[setBelowContentsVisible] Redux로 오버레이 표시 - true로 변경');
|
|
setBelowContentsVisible(true);
|
|
|
|
if (videoPlayer.current?.showControls) {
|
|
videoPlayer.current.showControls();
|
|
}
|
|
|
|
dispatch(resetPlayerOverlays());
|
|
}
|
|
}, [shouldShowOverlays, dispatch]);
|
|
|
|
// PlayerPanel이 최상단이 될 때 오버레이 표시 (DetailPanel에서 복귀)
|
|
useEffect(() => {
|
|
if (isOnTop && !panelInfo.modal && !videoVerticalVisible) {
|
|
// 정확한 복귀 종류 구분:
|
|
// 1. HomePanel 복귀: modalPrevRef.current === true && prevIsTopPanelDetailFromPlayerRef.current === false
|
|
// 2. DetailPanel 복귀: prevIsTopPanelDetailFromPlayerRef.current === true
|
|
const isHomePanelReturn =
|
|
modalPrevRef.current === true && prevIsTopPanelDetailFromPlayerRef.current === false;
|
|
const isDetailPanelReturn = prevIsTopPanelDetailFromPlayerRef.current === true;
|
|
|
|
if (isDetailPanelReturn) {
|
|
dlog('[PlayerPanel] ✅ PlayerPanel 내부 DetailPanel에서 복귀함! - 오버레이 표시');
|
|
} else if (isHomePanelReturn) {
|
|
dlog('[PlayerPanel] 📺 HomePanel에서 복귀함 - 오버레이 표시');
|
|
// HomePanel에서 복귀 시 콘텐츠 타입에 따라 tabIndex 설정
|
|
dlog('[PlayerPanel] 🔄 HomePanel 복귀 - tabIndex를 콘텐츠 타입에 따라 설정');
|
|
if (tabContainerVersion === 2) {
|
|
if (panelInfoRef.current.shptmBanrTpNm === 'VOD') {
|
|
setTabIndexV2(2);
|
|
dlog('[PlayerPanel] 📝 VOD 콘텐츠 - tabIndexV2를 2로 설정됨');
|
|
} else {
|
|
setTabIndexV2(1);
|
|
dlog('[PlayerPanel] 📝 LIVE 콘텐츠 - tabIndexV2를 1로 설정됨');
|
|
}
|
|
}
|
|
} else {
|
|
dlog('[PlayerPanel] 🔄 그 외 복귀 - 오버레이 표시');
|
|
}
|
|
|
|
setSideContentsVisible(true);
|
|
dlog('[setBelowContentsVisible] 복귀 - true로 변경');
|
|
setBelowContentsVisible(true);
|
|
// VideoPlayer가 belowContentsVisible prop을 감지해서 자동으로 controls 표시함
|
|
|
|
// PlayerPanel 내부 DetailPanel에서 복귀 시에만 포커스 복원 시도
|
|
if (isDetailPanelReturn) {
|
|
const lastFocusedTargetId = panelInfo?.lastFocusedTargetId;
|
|
dlog(
|
|
'[PlayerPanel] 🎯 PlayerPanel DetailPanel 복귀 - lastFocusedTargetId:',
|
|
lastFocusedTargetId
|
|
);
|
|
|
|
if (lastFocusedTargetId) {
|
|
// ShopNowContents가 렌더링될 때까지 잠시 대기 후 포커스 복원
|
|
setTimeout(() => {
|
|
dlog('[PlayerPanel] 🔍 500ms 후 포커스 복원 시도:', lastFocusedTargetId);
|
|
Spotlight.focus(lastFocusedTargetId);
|
|
}, 500);
|
|
}
|
|
|
|
// 한 번 처리한 복귀 플래그는 즉시 해제해 중복 영향을 막는다.
|
|
prevIsTopPanelDetailFromPlayerRef.current = false;
|
|
}
|
|
}
|
|
}, [
|
|
isOnTop,
|
|
panelInfo.modal,
|
|
videoVerticalVisible,
|
|
panelInfo?.lastFocusedTargetId,
|
|
tabContainerVersion,
|
|
setTabIndexV2,
|
|
]);
|
|
|
|
// PopularShow에서 처음 호출할 때처럼 modal이 false인 상태에서 VOD/LIVE 구분
|
|
useEffect(() => {
|
|
if (isOnTop && !panelInfo.modal && !videoVerticalVisible && tabContainerVersion === 2) {
|
|
// DetailPanel에서 막 복귀한 경우 탭 인덱스를 초기화하지 않는다.
|
|
if (panelInfo?.detailPanelClosed) {
|
|
return;
|
|
}
|
|
|
|
dlog(
|
|
'[PlayerPanel] 📺 Modal false 상태 - shptmBanrTpNm:',
|
|
panelInfoRef.current?.shptmBanrTpNm
|
|
);
|
|
|
|
if (panelInfoRef.current?.shptmBanrTpNm === 'VOD') {
|
|
setTabIndexV2(2);
|
|
dlog('[PlayerPanel] 📝 VOD 콘텐츠 - tabIndexV2를 2로 설정됨');
|
|
} else {
|
|
setTabIndexV2(1);
|
|
dlog('[PlayerPanel] 📝 LIVE 콘텐츠 - tabIndexV2를 1로 설정됨');
|
|
}
|
|
}
|
|
}, [isOnTop, panelInfo.modal, videoVerticalVisible, tabContainerVersion]);
|
|
|
|
useEffect(() => {
|
|
// tabContainerVersion === 1일 때만 실행
|
|
if (tabContainerVersion !== 1) return;
|
|
|
|
const node = document.querySelector(`[data-spotlight-id=${TAB_CONTAINER_SPOTLIGHT_ID}]`);
|
|
|
|
if (!showSideContents || !node || videoVerticalVisible) return;
|
|
|
|
// NOTE 첫 진입 시에는 10초 후 탭이 닫히도록 설정
|
|
if (initialEnter) {
|
|
resetTimer(INITIAL_TIMEOUT);
|
|
}
|
|
|
|
const handleEvent = () => resetTimer(REGULAR_TIMEOUT);
|
|
TARGET_EVENTS.forEach((event) => node.addEventListener(event, handleEvent));
|
|
|
|
return () => {
|
|
TARGET_EVENTS.forEach((event) => node.removeEventListener(event, handleEvent));
|
|
|
|
if (timerId.current) {
|
|
clearTimer();
|
|
}
|
|
};
|
|
}, [
|
|
showSideContents,
|
|
videoVerticalVisible,
|
|
tabContainerVersion,
|
|
resetTimer,
|
|
initialEnter,
|
|
clearTimer,
|
|
]);
|
|
|
|
useEffect(() => {
|
|
if (initialEnter || !sideContentsVisible || videoVerticalVisible) return;
|
|
|
|
// NOTE button을 통해 탭을 연 경우 5초 후 탭이 닫히도록 설정
|
|
if (sideContentsVisible) {
|
|
resetTimer(REGULAR_TIMEOUT);
|
|
}
|
|
|
|
return () => {
|
|
if (timerId.current) {
|
|
clearTimer();
|
|
}
|
|
};
|
|
}, [sideContentsVisible]);
|
|
|
|
// TabContainerV2 자동 닫기 (tabIndex 1 → 2 변경 감지)
|
|
useEffect(() => {
|
|
// tabContainerVersion === 2일 때만 실행
|
|
if (tabContainerVersion !== 2) {
|
|
prevTabIndexV2.current = tabIndexV2;
|
|
return;
|
|
}
|
|
|
|
// modal === false일 때만 실행
|
|
if (panelInfo?.modal) {
|
|
prevTabIndexV2.current = tabIndexV2;
|
|
return;
|
|
}
|
|
|
|
// tabIndexV2가 1에서 2로 정확하게 변경되는 시점만 감지
|
|
const isTransitionedTo2 = prevTabIndexV2.current === 1 && tabIndexV2 === 2;
|
|
prevTabIndexV2.current = tabIndexV2;
|
|
|
|
if (!isTransitionedTo2) {
|
|
if (timerIdV2.current) {
|
|
dlog('[TabContainerV2] 타이머 클리어 - tabIndex가 2가 아님', tabIndexV2);
|
|
clearTimerV2();
|
|
}
|
|
return;
|
|
}
|
|
|
|
dlog('[TabContainerV2] tabIndex 1 → 2 감지, 타이머 시작');
|
|
|
|
if (!belowContentsVisible || videoVerticalVisible) {
|
|
dlog(
|
|
'[TabContainerV2] early return - belowContentsVisible 또는 videoVerticalVisible 조건 불만족'
|
|
);
|
|
return;
|
|
}
|
|
|
|
// tabIndex 1 → 2로 변경된 정확한 시점에 30초 타이머 시작
|
|
dlog('[TabContainerV2] 30초 타이머 시작');
|
|
resetTimerV2(REGULAR_TIMEOUT);
|
|
|
|
return () => {
|
|
// cleanup: tabIndex가 2가 아니거나 오버레이가 사라질 때만 타이머 클리어
|
|
if (!belowContentsVisible || videoVerticalVisible || tabIndexV2 !== 2) {
|
|
if (timerIdV2.current) {
|
|
dlog('[TabContainerV2] cleanup - 타이머 클리어');
|
|
clearTimerV2();
|
|
}
|
|
}
|
|
};
|
|
}, [
|
|
tabContainerVersion,
|
|
tabIndexV2,
|
|
belowContentsVisible,
|
|
videoVerticalVisible,
|
|
panelInfo?.modal,
|
|
]);
|
|
|
|
// TabIndex 1 자동 다음 단계로 이동
|
|
useEffect(() => {
|
|
// tabIndex === 1일 때만 실행
|
|
if (tabIndexV2 !== 1 || !belowContentsVisible || videoVerticalVisible) {
|
|
if (timerIdTabAutoAdvance.current) {
|
|
clearTimerTabAutoAdvance();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// 10초 후 tabIndex를 2로 변경
|
|
resetTimerTabAutoAdvance(10000);
|
|
|
|
return () => {
|
|
if (timerIdTabAutoAdvance.current) {
|
|
clearTimerTabAutoAdvance();
|
|
}
|
|
};
|
|
}, [
|
|
tabIndexV2,
|
|
belowContentsVisible,
|
|
videoVerticalVisible,
|
|
resetTimerTabAutoAdvance,
|
|
clearTimerTabAutoAdvance,
|
|
]);
|
|
|
|
useLayoutEffect(() => {
|
|
const videoContainer = document.querySelector(`.${css.videoContainer}`);
|
|
|
|
if (panelInfo.thumbnail && !videoVerticalVisible) {
|
|
videoContainer.style.background = `url(${panelInfo.thumbnail}) center/contain no-repeat`;
|
|
videoContainer.style.backgroundColor = 'black';
|
|
}
|
|
|
|
if (broadcast.type === 'videoError' && videoThumbnailUrl) {
|
|
videoContainer.style.background = `url(${videoThumbnailUrl}) center/contain no-repeat`;
|
|
videoContainer.style.backgroundColor = 'black';
|
|
}
|
|
}, [panelInfo.thumbnail, broadcast]);
|
|
|
|
const isPlayer = useMemo(() => {
|
|
if (!panelInfo?.modal) {
|
|
return 'full player';
|
|
}
|
|
|
|
switch (panels[0].name) {
|
|
case 'categorypanel':
|
|
return 'category';
|
|
case 'mypagepanel':
|
|
return 'my page';
|
|
case 'searchpanel':
|
|
return 'search';
|
|
case 'hotpickpanel':
|
|
return 'hot picks';
|
|
case 'featuredbrandspanel':
|
|
return 'featured brands';
|
|
case 'trendingnowpanel':
|
|
return 'trending now';
|
|
case 'playerpanel':
|
|
return 'home';
|
|
}
|
|
}, [panelInfo.modal, panels]);
|
|
|
|
const createLogParams = useCallback(
|
|
(visible) => {
|
|
if (videoLoaded && isShowType) {
|
|
if (showDetailInfo?.[0]) {
|
|
return {
|
|
visible,
|
|
showType: isShowType,
|
|
player: isPlayer,
|
|
category: showDetailInfo[0].showCatNm,
|
|
showId: showDetailInfo[0].showId,
|
|
showTitle: showDetailInfo[0].showNm,
|
|
partner: showDetailInfo[0].patncNm,
|
|
contextName: Config.LOG_CONTEXT_NAME.SHOW,
|
|
messageId: Config.LOG_MESSAGE_ID.SHOWVIEW,
|
|
};
|
|
} else if (playListInfo?.[selectedIndex]) {
|
|
const currentItem = playListInfo[selectedIndex];
|
|
return {
|
|
visible,
|
|
showType: isShowType,
|
|
player: isPlayer,
|
|
category: currentItem.catNm,
|
|
showId: currentItem.showId,
|
|
contentTitle: currentItem.showNm,
|
|
partner: currentItem.patncNm,
|
|
contextName: Config.LOG_CONTEXT_NAME.SHOW,
|
|
messageId: Config.LOG_MESSAGE_ID.SHOWVIEW,
|
|
};
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
[isShowType, videoLoaded, showDetailInfo?.[0]?.showId, playListInfo?.[selectedIndex]?.showId]
|
|
);
|
|
|
|
// isVODPaused 상태 변경 시에만 로그를 보냄
|
|
useEffect(() => {
|
|
if (showDetailInfo?.[0]) {
|
|
const params = createLogParams(!isVODPaused);
|
|
if (params) {
|
|
dispatch(sendLogTotalRecommend(params));
|
|
}
|
|
} else if (playListInfo?.[selectedIndex]) {
|
|
const params = createLogParams(true);
|
|
if (params) {
|
|
dispatch(sendLogTotalRecommend(params));
|
|
}
|
|
}
|
|
}, [isVODPaused, createLogParams, showDetailInfo]);
|
|
|
|
// 컴포넌트 언마운트 시에만 로그를 보냄
|
|
useEffect(() => {
|
|
return () => {
|
|
const params = createLogParams(false);
|
|
if (params) {
|
|
dispatch(sendLogTotalRecommend(params));
|
|
}
|
|
};
|
|
}, [createLogParams, dispatch, showDetailInfo]);
|
|
|
|
const containerClassName = classNames(
|
|
css.videoContainer,
|
|
panelInfo.modal && css.modal,
|
|
panelInfo.shouldShrinkTo1px && css.shrinkTo1px,
|
|
// PlayerPanel이 최상단 아니고, 최상단이 DetailPanel(from Player)이면 비디오 보이도록
|
|
!isOnTop && isTopPanelDetailFromPlayer && css['background-visible'],
|
|
// PlayerPanel이 최상단 아니고, 위 조건 아니면 1px로 숨김
|
|
!isOnTop && !isTopPanelDetailFromPlayer && css.background,
|
|
!captionEnable && css.hideSubtitle
|
|
);
|
|
|
|
return (
|
|
<TPanel
|
|
isTabActivated={false}
|
|
{...props}
|
|
className={containerClassName}
|
|
handleCancel={onClickBack}
|
|
spotlightId={spotlightId}
|
|
>
|
|
<Container
|
|
spotlightRestrict="self-only"
|
|
spotlightId="spotlightId-video-contaienr"
|
|
onKeyDown={onKeyDown}
|
|
// onClick={() => {
|
|
// if (!panelInfo?.modal && isOnTop && panelInfo?.shptmBanrTpNm !== 'MEDIA') {
|
|
// setBelowContentsVisible((prev) => !prev);
|
|
// }
|
|
// }}
|
|
>
|
|
<div
|
|
ref={focusReturnRef}
|
|
tabIndex={-1}
|
|
aria-hidden="true"
|
|
style={{ width: 1, height: 1, opacity: 0, position: 'absolute' }}
|
|
/>
|
|
{isReadyToPlay && (
|
|
<VideoPlayer
|
|
key={currentPlayingUrl}
|
|
setApiProvider={getPlayer}
|
|
disabled={panelInfo.modal}
|
|
onEnded={onEnded}
|
|
noAutoPlay={cannotPlay}
|
|
autoCloseTimeout={3000}
|
|
onBackButton={onClickBack}
|
|
spotlightDisabled={panelInfo.modal}
|
|
isYoutube={isYoutube}
|
|
src={currentPlayingUrl}
|
|
style={panelInfo.modal ? modalStyle : {}}
|
|
modalScale={panelInfo.modal ? modalScale : 1}
|
|
modalClassName={panelInfo.modal && panelInfo.modalClassName}
|
|
handleIndicatorDownClick={handleIndicatorDownClick}
|
|
handleIndicatorUpClick={handleIndicatorUpClick}
|
|
onError={mediainfoHandler}
|
|
onTimeUpdate={mediainfoHandler}
|
|
onLoadedData={mediainfoHandler}
|
|
onLoadedMetadata={mediainfoHandler}
|
|
onDurationChange={mediainfoHandler}
|
|
reactPlayerConfig={reactPlayerSubtitleConfig}
|
|
thumbnailUrl={videoLoaded ? '' : videoThumbnailUrl}
|
|
videoComponent={
|
|
(typeof window === 'object' && !window.PalmSystem) || isYoutube ? TReactPlayer : Media
|
|
}
|
|
liveTotalTime={liveTotalTime}
|
|
currentLiveTimeSeconds={currentLiveTimeSeconds}
|
|
countryCode={countryCode}
|
|
// VideoOverlay props
|
|
type={isShowType}
|
|
panelInfo={panelInfo}
|
|
captionEnable={captionEnable}
|
|
orderPhnNo={orderPhoneNumber}
|
|
disclaimer={shopNowInfo && shopNowInfo[smallestOffsetHourIndex]?.disclaimer}
|
|
themeProductInfos={themeProductInfos ? themeProductInfos : hotelInfos}
|
|
detailThemeProductImageLength={productImageLength}
|
|
playListInfo={playListInfo && playListInfo}
|
|
selectedIndex={selectedIndex}
|
|
qrCurrentItem={qrCurrentItem}
|
|
setIsSubtitleActive={setIsSubtitleActive}
|
|
setSideContentsVisible={setSideContentsVisible}
|
|
sideContentsVisible={sideContentsVisible}
|
|
setBelowContentsVisible={setBelowContentsVisible}
|
|
belowContentsVisible={belowContentsVisible}
|
|
videoVerticalVisible={videoVerticalVisible}
|
|
setCurrentTime={setCurrentTime}
|
|
setIsVODPaused={setIsVODPaused}
|
|
broadcast={broadcast}
|
|
tabContainerVersion={tabContainerVersion}
|
|
tabIndexV2={tabIndexV2}
|
|
dispatch={dispatch}
|
|
videoPlayState={videoPlayState}
|
|
>
|
|
{typeof window === 'object' && window.PalmSystem && (
|
|
<source src={currentPlayingUrl} type={videoType} />
|
|
)}
|
|
{isSubtitleActive &&
|
|
!panelInfo.modal &&
|
|
captionEnable &&
|
|
typeof window === 'object' &&
|
|
window.PalmSystem &&
|
|
currentSubtitleBlob && <track kind="subtitles" src={currentSubtitleBlob} default />}
|
|
</VideoPlayer>
|
|
)}
|
|
|
|
{chatVisible && (
|
|
<PlayerOverlayChat
|
|
currentTime={currentTime}
|
|
videoVerticalVisible={videoVerticalVisible}
|
|
imageQRCodeUrl={playListInfo[selectedIndex]?.chatQrImgUrl}
|
|
QRCodeUrl={playListInfo[selectedIndex]?.chatUrl}
|
|
/>
|
|
)}
|
|
|
|
{tabContainerVersion === 1 &&
|
|
currentSideButtonStatus &&
|
|
!videoVerticalVisible &&
|
|
playListInfo && (
|
|
<PlayerTabButton
|
|
setSideContentsVisible={setSideContentsVisible}
|
|
sideContentsVisible={sideContentsVisible}
|
|
videoType={isShowType}
|
|
/>
|
|
)}
|
|
|
|
{tabContainerVersion === 1 && showSideContents && (
|
|
<TabContainer
|
|
spotlightId={TAB_CONTAINER_SPOTLIGHT_ID}
|
|
panelInfo={panelInfo}
|
|
shopNowInfo={shopNowInfo}
|
|
playListInfo={playListInfo}
|
|
selectedIndex={selectedIndex}
|
|
setSelectedIndex={setSelectedIndex}
|
|
liveChannelInfos={liveChannelInfos || liveShowInfos}
|
|
videoVerticalVisible={videoVerticalVisible}
|
|
handleItemFocus={handleItemFocus}
|
|
prevChannelIndex={prevChannelIndex}
|
|
currentTime={currentTime}
|
|
/>
|
|
)}
|
|
|
|
{/* {shouldShowBelowTab && (
|
|
<>
|
|
{belowTabMode === 'liveShow' && (
|
|
<LiveShowContainer
|
|
panelInfo={panelInfo}
|
|
liveInfos={playListInfo}
|
|
currentTime={currentTime}
|
|
setSelectedIndex={setSelectedIndex}
|
|
videoVerticalVisible={videoVerticalVisible}
|
|
currentVideoShowId={playListInfo && playListInfo[selectedIndex]?.showId}
|
|
handleItemFocus={handleItemFocus}
|
|
onLiveChannelButtonClick={() => setBelowTabMode('shopNowButton')}
|
|
tabTitle={[
|
|
$L('SHOP NOW'),
|
|
panelInfo?.shptmBanrTpNm === 'LIVE' ? $L('LIVE CHANNEL') : $L('FEATURED SHOWS'),
|
|
]}
|
|
selectedIndex={selectedIndex}
|
|
tabIndex={1}
|
|
/>
|
|
)}
|
|
{belowTabMode === 'shopNowButton' && (
|
|
<ShopNowButton onClick={() => setBelowTabMode('shopNow')} />
|
|
)}
|
|
{belowTabMode === 'shopNow' && (
|
|
<ShopNowContainer
|
|
panelInfo={panelInfo}
|
|
liveInfos={playListInfo}
|
|
currentTime={currentTime}
|
|
setSelectedIndex={setSelectedIndex}
|
|
videoVerticalVisible={videoVerticalVisible}
|
|
currentVideoShowId={playListInfo && playListInfo[selectedIndex]?.showId}
|
|
handleItemFocus={handleItemFocus}
|
|
/>
|
|
)}
|
|
</>
|
|
)} */}
|
|
|
|
{tabContainerVersion === 2 && showBelowContents && (
|
|
<TabContainerV2
|
|
panelInfo={panelInfo}
|
|
playListInfo={playListInfo}
|
|
shopNowInfo={shopNowInfo}
|
|
selectedIndex={selectedIndex}
|
|
setSelectedIndex={setSelectedIndex}
|
|
liveChannelInfos={liveChannelInfos || liveShowInfos}
|
|
videoVerticalVisible={videoVerticalVisible}
|
|
handleItemFocus={handleItemFocus}
|
|
prevChannelIndex={prevChannelIndex}
|
|
currentTime={currentTime}
|
|
spotlightId={TAB_CONTAINER_V2_SPOTLIGHT_ID}
|
|
tabIndex={tabIndexV2}
|
|
onShopNowButtonClick={() => setTabIndexV2(0)}
|
|
onLiveChannelButtonClick={() => setTabIndexV2(2)}
|
|
onLiveNext={() => setTabIndexV2(1)}
|
|
onTabClose={(newTabIndex) => setTabIndexV2(newTabIndex)}
|
|
tabVisible={belowContentsVisible}
|
|
/>
|
|
)}
|
|
</Container>
|
|
|
|
{activePopup === ACTIVE_POPUP.alertPopup && (
|
|
<TPopUp
|
|
kind="textPopup"
|
|
hasText
|
|
hasButton
|
|
text={$L(
|
|
"To view the subtitles, please change the setting to 'On' in the system settings menu."
|
|
)}
|
|
button2Text={$L('OK')}
|
|
open={popupVisible}
|
|
onClose={handlePopupClose}
|
|
></TPopUp>
|
|
)}
|
|
</TPanel>
|
|
);
|
|
};
|
|
|
|
const propsAreEqual = (prev, next) => {
|
|
const keys = Object.keys(prev);
|
|
const nextKeys = Object.keys(next);
|
|
// if (!next.isOnTop) {
|
|
// //ignore event on background
|
|
// return true;
|
|
// }
|
|
if (keys.length !== nextKeys.length) {
|
|
return false;
|
|
}
|
|
for (let i = 0; i < keys.length; i++) {
|
|
if (prev[keys[i]] !== next[keys[i]]) {
|
|
if (JSON.stringify(prev[keys[i]]) === JSON.stringify(next[keys[i]])) {
|
|
continue;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
export default React.memo(PlayerPanel, propsAreEqual);
|