Files
shoptime/com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx
optrader af30f8c688 [251115] fix: MediaPanel.v3.jsx 비디오재생-1
🕐 커밋 시간: 2025. 11. 15. 12:22:59

📊 변경 통계:
  • 총 파일: 12개
  • 추가: +246줄
  • 삭제: -27줄

📁 추가된 파일:
  + com.twin.app.shoptime/src/utils/focusPanelGuide.js

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/actions/actionTypes.js
  ~ com.twin.app.shoptime/src/actions/mediaActions.js
  ~ com.twin.app.shoptime/src/actions/panelActions.js
  ~ com.twin.app.shoptime/src/reducers/panelReducer.js
  ~ com.twin.app.shoptime/src/utils/SpotlightIds.js
  ~ com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx
  ~ com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx
  ~ com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.module.less
  ~ com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v3.jsx
  ~ com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/YouMayAlsoLike/YouMayAlsoLike.jsx
  ~ com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx

🔧 함수 변경 내용:
  📄 com.twin.app.shoptime/src/actions/panelActions.js (javascript):
     Added: resetPanels()
  📄 com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx (javascript):
    🔄 Modified: extractProductMeta()
  📄 com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v3.jsx (javascript):
    🔄 Modified: Spottable()
  📄 com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/YouMayAlsoLike/YouMayAlsoLike.jsx (javascript):
    🔄 Modified: SpotlightContainerDecorator()
  📄 com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx (javascript):
    🔄 Modified: normalizeModalStyle()
  📄 com.twin.app.shoptime/src/utils/focusPanelGuide.js (javascript):
     Added: DetailPanel(), handleProductSelect()

🔧 주요 변경 내용:
  • 타입 시스템 안정성 강화
  • 핵심 비즈니스 로직 개선
  • 공통 유틸리티 함수 최적화
2025-11-15 12:23:00 +09:00

2346 lines
74 KiB
JavaScript

import React, {
useCallback,
useEffect,
useImperativeHandle,
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 {
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, focusPanel } from '../../actions/panelActions';
import {
CLEAR_PLAYER_INFO,
getChatLog,
getSubTitle,
startVideoPlayer,
} from '../../actions/playActions';
import { resetPlayerOverlays } from '../../actions/videoPlayActions';
import {
finishModalMediaForce,
minimizeModalMedia,
pauseModalMedia,
restoreModalMedia,
resumeModalMedia,
startMediaPlayer,
switchMediaToFullscreen,
switchMediaToModal,
} from '../../actions/mediaActions';
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.v3';
import usePrevious from '../../hooks/usePrevious';
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 css from './MediaPanel.v3.module.less';
const Container = SpotlightContainerDecorator(
{ enterTo: 'default-element', preserveld: true },
'div'
);
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,
},
};
// last time error
const VIDEO_END_ACTION_DELAY = 1500;
const VIDEO_MARGIN = 4;
const normalizeModalStyle = (style = {}) => {
if (!style || typeof style !== 'object') {
return {};
}
const parseValue = (value) => {
if (typeof value === 'number') {
return value;
}
if (typeof value === 'string') {
const parsed = parseFloat(value);
return Number.isNaN(parsed) ? 0 : parsed;
}
return 0;
};
const baseWidth = parseValue(style.width);
const baseHeight = parseValue(style.height);
const baseLeft = parseValue(style.left);
const baseTop = parseValue(style.top);
const adjustedWidth = Math.max(baseWidth - VIDEO_MARGIN * 2, 0);
const adjustedHeight = Math.max(baseHeight - VIDEO_MARGIN * 2, 0);
const adjustedLeft = baseLeft + VIDEO_MARGIN;
const adjustedTop = baseTop + VIDEO_MARGIN;
return {
...style,
width: baseWidth ? `${adjustedWidth}px` : style.width,
height: baseHeight ? `${adjustedHeight}px` : style.height,
left: style.left !== undefined ? `${adjustedLeft}px` : style.left,
top: style.top !== undefined ? `${adjustedTop}px` : style.top,
};
};
const MediaPanel = React.forwardRef(
({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props }, ref) => {
const dispatch = useDispatch();
const { USE_STATE, USE_SELECTOR } = useWhyDidYouUpdate(spotlightId, {
isTabActivated,
panelInfo,
isOnTop,
...props,
});
const videoPlayer = useRef(null);
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 [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 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 = usePrevious(nowMenu);
const entryMenu = USE_SELECTOR('entryMenu', (state) => state.common.menu.entryMenu);
const [videoLoaded, setVideoLoaded] = USE_STATE('videoLoaded', false);
const entryMenuRef = usePrevious(entryMenu);
const panelInfoRef = usePrevious(panelInfo);
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('[MediaPanel] isOnTop:', {
// isOnTop,
// panelInfo
// });
// if (panelInfo && panelInfo.modal) {
// if (!isOnTop) {
// console.log('[MediaPanel] Not on top - pausing video');
// dispatch(pauseModalMedia());
// } else if (isOnTop && panelInfo.isPaused) {
// console.log('[MediaPanel] Back on top - resuming video');
// dispatch(resumeModalMedia());
// }
// }
// },[isOnTop, panelInfo])
// MediaPanel.jsx의 라인 313-327 useEffect 수정
// MEDIA 타입은 isOnTop 체크 하지 않음 (패널 구조 복잡도 때문에)
useEffect(() => {
console.log('[MediaPanel] isOnTop:', {
isOnTop,
panelInfo,
});
if (panelInfo && panelInfo.modal && panelInfo?.shptmBanrTpNm !== 'MEDIA') {
if (!isOnTop) {
console.log('[MediaPanel] Not on top - pausing video');
dispatch(pauseModalMedia());
} else if (isOnTop && panelInfo.isPaused) {
console.log('[MediaPanel] Back on top - resuming video');
dispatch(resumeModalMedia());
}
}
}, [isOnTop, panelInfo]);
// 새로운 useEffect 추가 (라인 328 이후)
useEffect(() => {
// modal 여부와 관계없이 videoPlayer가 있고 isPaused 값이 명시적일 때 제어
if (videoPlayer.current && panelInfo?.isPaused !== undefined) {
if (panelInfo.isPaused === true) {
videoPlayer.current.pause();
} else if (panelInfo.isPaused === false) {
videoPlayer.current.play();
}
}
}, [panelInfo?.isPaused]);
// creating live log params
useEffect(() => {
if (currentLiveShowInfo && Object.keys(currentLiveShowInfo).length > 0) {
if (currentLiveShowInfo.showId !== panelInfo?.showId) {
dispatch(
updatePanel({
name: panel_names.MEDIA_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));
},
[dispatch]
);
const handleClickBack = useCallback(
(ev, isEnd) => {
//modal로부터 Full 전환된 경우 다시 preview 모드로 돌아감.
if (panelInfo.modalContainerId && !panelInfo.modal) {
dispatch(
startMediaPlayer({
...panelInfo,
modal: true,
modalClassName: '',
})
);
videoPlayer.current?.hideControls();
setSelectedIndex(backupInitialIndex);
if (panelInfo.shptmBanrTpNm === 'MEDIA') {
dispatch(
updatePanel({
name: panel_names.DETAIL_PANEL,
panelInfo: {
launchedFromPlayer: false,
},
})
);
}
// 모달 복귀 시 ProductVideo로 포커스 이동 (ProductVideo.v3.jsx에서 처리)
console.log(
'[MediaPanel] Back button pressed - returning to modal, focus will be handled by ProductVideo'
);
setTimeout(() => {
console.log('[MediaPanel] focusPanel ');
dispatch(focusPanel('DETAIL_PANEL', 'product-video-player'));
}, 100);
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, videoVerticalVisible, backupInitialIndex, panels]
);
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) {
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,
]);
// 최상단 패널 정보 (여러 useMemo에서 공통으로 사용)
const topPanel = useMemo(() => {
return panels[panels.length - 1];
}, [panels]);
// 최상단 패널이 DetailPanel이고 MediaPanel에서 진입했는지 확인
const isTopPanelDetailFromPlayer = useMemo(() => {
return (
topPanel?.name === panel_names.DETAIL_PANEL &&
topPanel?.panelInfo?.launchedFromPlayer === true
);
}, [topPanel]);
const cannotPlay = useMemo(() => {
// URL이 있으면 항상 재생 가능 (isOnTop 조건 제거)
return false;
}, []);
const getPlayer = useCallback((ref) => {
videoPlayer.current = ref;
}, []);
const playVideo = useCallback(() => {
videoPlayer.current?.play?.();
}, []);
const pauseVideo = useCallback(() => {
videoPlayer.current?.pause?.();
}, []);
const seekVideo = useCallback((seconds = 0) => {
videoPlayer.current?.seek?.(seconds);
}, []);
const showControls = useCallback(() => {
videoPlayer.current?.showControls?.();
}, []);
const hideControls = useCallback(() => {
videoPlayer.current?.hideControls?.();
}, []);
const toggleControls = useCallback(() => {
videoPlayer.current?.toggleControls?.();
}, []);
const getCurrentMediaState = useCallback(() => {
return videoPlayer.current?.getMediaState?.();
}, []);
const openModal = useCallback(
(params = {}) => {
const {
modal = true,
modalContainerId = 'product-video-player',
modalClassName = '',
spotlightDisable = true,
...rest
} = params;
dispatch(
startMediaPlayer({
modal,
modalContainerId,
modalClassName,
spotlightDisable,
...rest,
})
);
},
[dispatch]
);
const close = useCallback(() => {
dispatch(finishModalMediaForce());
}, [dispatch]);
const enterFullscreen = useCallback(() => {
dispatch(switchMediaToFullscreen());
}, [dispatch]);
const enterModal = useCallback(
(modalContainerId = 'product-video-player', modalClassName = '') => {
dispatch(switchMediaToModal(modalContainerId, modalClassName));
},
[dispatch]
);
const minimizeViaRef = useCallback(() => {
dispatch(minimizeModalMedia());
}, [dispatch]);
const restoreViaRef = useCallback(() => {
dispatch(restoreModalMedia());
}, [dispatch]);
useImperativeHandle(
ref,
() => ({
playVideo,
pauseVideo,
seekVideo,
showControls,
hideControls,
toggleControls,
getCurrentMediaState,
minimize: minimizeViaRef,
restore: restoreViaRef,
openModal,
close,
enterFullscreen,
enterModal,
getInternalPlayer: () => videoPlayer.current,
}),
[
playVideo,
pauseVideo,
seekVideo,
showControls,
hideControls,
toggleControls,
getCurrentMediaState,
minimizeViaRef,
restoreViaRef,
openModal,
close,
enterFullscreen,
enterModal,
]
);
/** 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 &&
showDetailInfo[0]?.showCatCd &&
fullVideolgCatCd !== showDetailInfo[0]?.showCatCd //기존에 호출했으면 안한다.
) {
dispatch(
getHomeFullVideoInfo({
lgCatCd: showDetailInfo[0].showCatCd,
})
);
}
if (
panelInfo.shptmBanrTpNm === 'VOD' &&
showDetailInfo &&
showDetailInfo[0] &&
showDetailInfo[0].showId &&
showDetailInfo[0].patnrId
) {
if (!featuredShowsInfos || Object.keys(featuredShowsInfos).length === 0) {
setPlayListInfo(showDetailInfo);
}
setShopNowInfo(showDetailInfo[0].productInfos);
saveToLocalSettings(showDetailInfo[0].showId, showDetailInfo[0].patnrId);
}
}, [showDetailInfo]);
//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();
}
}, [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') {
console.log('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);
}
}
},
[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) {
console.log('[MediaPanel] Condition 2.5: Using saved modalStyle from expand');
const shrinkInfo = panelInfo.playerState?.shrinkInfo;
// 저장된 modalStyle 사용 (top, left 포함)
if (shrinkInfo?.modalStyle) {
setModalStyle(normalizeModalStyle(shrinkInfo.modalStyle));
setModalScale(panelInfo.modalScale || shrinkInfo.modalScale);
} else {
setModalStyle(normalizeModalStyle(panelInfo.modalStyle));
setModalScale(panelInfo.modalScale);
}
// DOM 렌더링 후 플래그 제거
if (typeof window !== 'undefined') {
window.requestAnimationFrame(() => {
window.requestAnimationFrame(() => {
console.log('[MediaPanel] Condition 2.5: Removing skipFlag after DOM render');
dispatch(
updatePanel({
name: panel_names.MEDIA_PANEL,
panelInfo: {
...panelInfo,
skipModalStyleRecalculation: false,
},
})
);
});
});
}
}
if (
panelInfo.modal &&
!panelInfo.shouldShrinkTo1px &&
panelInfo.modalContainerId &&
(lastPanelAction === 'previewPush' || lastPanelAction === 'previewUpdate')
) {
console.log('[MediaPanel] 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 baseModalStyle = {
width: `${width}px`,
height: `${height}px`,
top: `${top}px`,
left: `${left}px`,
position: 'fixed',
overflow: 'visible',
};
const normalizedStyle = normalizeModalStyle(baseModalStyle);
setModalStyle(normalizedStyle);
let scale = 1;
if (typeof window === 'object') {
scale = width / window.innerWidth;
setModalScale(scale);
}
dispatch(
updatePanel({
name: panel_names.MEDIA_PANEL,
panelInfo: {
modalStyle: normalizedStyle,
modalScale: scale,
},
})
);
} else {
console.log('[MediaPanel] Condition 1: Node not found, using saved modalStyle');
setModalStyle(normalizeModalStyle(panelInfo.modalStyle));
setModalScale(panelInfo.modalScale);
}
} else if (panelInfo.shouldShrinkTo1px) {
console.log('[MediaPanel] Condition 2: Shrinking - clearing modalStyle');
// 축소 상태: 인라인 스타일 제거 (CSS만 적용)
setModalStyle({});
setModalScale(1);
} else if (isOnTop && !panelInfo.modal && videoPlayer.current) {
console.log('[MediaPanel] 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;
}
// MEDIA 타입일 때: panelInfo.showUrl 사용
if (panelInfo?.shptmBanrTpNm === 'MEDIA') {
console.log('[MediaPanel]-LoadingVideo 📺 MEDIA URL:', {
showUrl: panelInfo?.showUrl?.substring(0, 50),
prdtId: panelInfo?.prdtId,
});
return panelInfo?.showUrl;
}
// 기타 타입: playListInfo 사용
const url = playListInfo && playListInfo[selectedIndex]?.showUrl;
if (url) {
console.log('[MediaPanel]-LoadingVideo 🎬 PlayList URL:', {
url: url.substring(0, 50),
});
}
return url;
}, [playListInfo, selectedIndex, broadcast, panelInfo?.shptmBanrTpNm, panelInfo?.showUrl]);
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) {
console.log('[MediaPanel]-LoadingVideo ❌ isReadyToPlay = false (no URL)');
return false;
}
if (!Config.DEBUG_VIDEO_SUBTITLE_TEST && currentSubtitleUrl && !currentSubtitleBlob) {
console.log('[MediaPanel]-LoadingVideo ❌ isReadyToPlay = false (subtitle not loaded):', {
currentSubtitleUrl,
currentSubtitleBlob: !!currentSubtitleBlob,
});
return false;
}
console.log('[MediaPanel]-LoadingVideo ✅ isReadyToPlay = true');
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]);
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(() => {
return false;
}, []);
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.MEDIA_PANEL,
panelInfo: {
chanId: playListInfo[newIndex].chanId,
patnrId: playListInfo[newIndex].patnrId,
showId: playListInfo[newIndex].showId,
shptmBanrTpNm: panelInfo?.shptmBanrTpNm,
isIndicatorByClick: true,
},
})
);
}
}
}, [dispatch, playListInfo, selectedIndex, 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.MEDIA_PANEL,
panelInfo: {
chanId: playListInfo[newIndex].chanId,
patnrId: playListInfo[newIndex].patnrId,
showId: playListInfo[newIndex].showId,
shptmBanrTpNm: panelInfo?.shptmBanrTpNm,
isIndicatorByClick: true,
},
})
);
}
}
}, [dispatch, playListInfo, selectedIndex, 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 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(() => {
Spotlight.resume();
if (panelInfoRef.current.modal) {
videoPlayer.current.play();
} else {
dispatch(PanelActions.popPanel(panel_names.MEDIA_PANEL));
}
}, VIDEO_END_ACTION_DELAY);
e?.stopPropagation();
e?.preventDefault();
return;
}
}, []);
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 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]);
// Redux로 오버레이 숨김
useEffect(() => {
if (shouldHideOverlays) {
console.log('[MediaPanel] shouldHideOverlays true - 오버레이 숨김');
if (videoPlayer.current?.hideControls) {
videoPlayer.current.hideControls();
}
dispatch(resetPlayerOverlays());
}
}, [shouldHideOverlays, dispatch]);
// Redux로 오버레이 표시
useEffect(() => {
if (shouldShowOverlays) {
console.log('[MediaPanel] shouldShowOverlays true - 오버레이 표시');
if (videoPlayer.current?.showControls) {
videoPlayer.current.showControls();
}
dispatch(resetPlayerOverlays());
}
}, [shouldShowOverlays, dispatch]);
useEffect(() => {
if (panelInfoRef.current?.modal && !panelInfo.modal && isOnTop && !videoVerticalVisible) {
console.log(
'[MediaPanel] Modal to fullscreen transition detected - focus will be handled by ProductVideo'
);
// 포커스를 ProductVideo에 맡김 (ProductVideo.v3.jsx에서 처리)
// PLAYER_BACK_BUTTON으로 포커스하지 않음
}
}, [panelInfo.modal, isOnTop, videoVerticalVisible]);
useLayoutEffect(() => {
const videoContainer = document.querySelector(`.${css.videoContainer}`);
if (panelInfo.thumbnail && !videoVerticalVisible) {
videoContainer.style.background = `url(${panelInfo.thumbnail}) center center / contain no-repeat`;
videoContainer.style.backgroundColor = 'black';
}
if (broadcast.type === 'videoError' && videoThumbnailUrl) {
videoContainer.style.background = `url(${videoThumbnailUrl}) center 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,
// MediaPanel이 최상단 아니고, 최상단이 DetailPanel(from Player)이면 비디오 보이도록
!isOnTop && isTopPanelDetailFromPlayer && css['background-visible'],
// MediaPanel이 최상단 아니고, 위 조건 아니면 1px로 숨김
!isOnTop && !isTopPanelDetailFromPlayer && css.background,
!captionEnable && css.hideSubtitle
);
return (
<TPanel
isTabActivated={false}
{...props}
className={containerClassName}
handleCancel={handleClickBack}
spotlightId={spotlightId}
>
<Container
spotlightRestrict={panelInfo?.modal ? 'self-only' : undefined}
spotlightId="spotlightId-video-contaienr"
onKeyDown={onKeyDown}
// onClick={() => {
// if (!panelInfo?.modal && isOnTop && panelInfo?.shptmBanrTpNm !== 'MEDIA') {
// setBelowContentsVisible((prev) => !prev);
// }
// }}
>
{(() => {
if (isReadyToPlay) {
console.log('[MediaPanel]-LoadingVideo 🎬 VideoPlayer 렌더링:', {
src: currentPlayingUrl?.substring(0, 50),
disabled: panelInfo.modal,
cannotPlay,
isYoutube,
videoComponent:
(typeof window === 'object' && !window.PalmSystem) || isYoutube
? 'TReactPlayer'
: 'Media',
});
} else {
console.log('[MediaPanel]-LoadingVideo 🚫 VideoPlayer 렌더링 스킵됨');
}
return null;
})()}
{isReadyToPlay && (
<div className={css.videoFrame}>
<VideoPlayer
setApiProvider={getPlayer}
disabled={panelInfo.modal}
onEnded={onEnded}
noAutoPlay={false}
autoCloseTimeout={3000}
onBackButton={handleClickBack}
spotlightDisabled={false}
isYoutube={isYoutube}
src={currentPlayingUrl}
style={panelInfo.modal ? modalStyle : {}}
modalScale={panelInfo.modal ? modalScale : 1}
modalClassName={panelInfo.modal && panelInfo.modalClassName}
spotlightId={panelInfo.modalContainerId || spotlightId}
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}
videoVerticalVisible={videoVerticalVisible}
setCurrentTime={setCurrentTime}
setIsVODPaused={setIsVODPaused}
broadcast={broadcast}
dispatch={dispatch}
>
{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>
</div>
)}
{chatVisible && (
<PlayerOverlayChat
currentTime={currentTime}
videoVerticalVisible={videoVerticalVisible}
imageQRCodeUrl={playListInfo[selectedIndex]?.chatQrImgUrl}
QRCodeUrl={playListInfo[selectedIndex]?.chatUrl}
/>
)}
</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(MediaPanel, propsAreEqual);