From d2c149c914da33719415232f9a3156e0671d2af9 Mon Sep 17 00:00:00 2001 From: optrader Date: Mon, 24 Nov 2025 10:45:28 +0900 Subject: [PATCH] [251124] fix: PlayerPanel videoState updated MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ๐Ÿ• ์ปค๋ฐ‹ ์‹œ๊ฐ„: 2025. 11. 24. 10:45:28 ๐Ÿ“Š ๋ณ€๊ฒฝ ํ†ต๊ณ„: โ€ข ์ด ํŒŒ์ผ: 9๊ฐœ โ€ข ์ถ”๊ฐ€: +509์ค„ โ€ข ์‚ญ์ œ: -66์ค„ ๐Ÿ“ ์ˆ˜์ •๋œ ํŒŒ์ผ: ~ com.twin.app.shoptime/src/actions/mainActions.js ~ com.twin.app.shoptime/src/actions/playActions.js ~ com.twin.app.shoptime/src/api/TAxios.js ~ com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.js ~ com.twin.app.shoptime/src/reducers/playReducer.js ~ com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx ~ com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RollingUnit.jsx ~ com.twin.app.shoptime/src/views/HomePanel/HomePanel.jsx ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx ๐Ÿ”ง ํ•จ์ˆ˜ ๋ณ€๊ฒฝ ๋‚ด์šฉ: ๐Ÿ“„ com.twin.app.shoptime/src/actions/mainActions.js (javascript): โŒ Deleted: onSuccess(), onFail() ๐Ÿ“„ com.twin.app.shoptime/src/actions/playActions.js (javascript): ๐Ÿ”„ Modified: clearAllVideoTimers(), pauseModalVideo(), hideModalVideo() โŒ Deleted: CLEAR_PLAYER_INFO() ๐Ÿ“„ com.twin.app.shoptime/src/api/TAxios.js (javascript): ๐Ÿ”„ Modified: setTokenRefreshing(), createSafeApiThunk() ๐Ÿ“„ com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx (javascript): ๐Ÿ”„ Modified: SpotlightContainerDecorator() ๐Ÿ“„ com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RollingUnit.jsx (javascript): ๐Ÿ”„ Modified: createPanelInfo() ๐Ÿ”ง ์ฃผ์š” ๋ณ€๊ฒฝ ๋‚ด์šฉ: โ€ข ํ•ต์‹ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๊ฐœ์„  โ€ข API ์„œ๋น„์Šค ๋ ˆ์ด์–ด ๊ฐœ์„  โ€ข UI ์ปดํฌ๋„ŒํŠธ ์•„ํ‚คํ…์ฒ˜ ๊ฐœ์„  --- .../src/actions/mainActions.js | 36 +-- .../src/actions/playActions.js | 109 +++++-- com.twin.app.shoptime/src/api/TAxios.js | 12 +- .../src/components/VideoPlayer/VideoPlayer.js | 195 +++++++++++- .../src/reducers/playReducer.js | 80 ++++- .../views/HomePanel/HomeBanner/RandomUnit.jsx | 6 +- .../HomePanel/HomeBanner/RollingUnit.jsx | 282 +++++++----------- .../src/views/HomePanel/HomePanel.jsx | 25 ++ .../src/views/PlayerPanel/PlayerPanel.jsx | 214 ++++++++++++- 9 files changed, 700 insertions(+), 259 deletions(-) diff --git a/com.twin.app.shoptime/src/actions/mainActions.js b/com.twin.app.shoptime/src/actions/mainActions.js index 9239497a..5167f09c 100644 --- a/com.twin.app.shoptime/src/actions/mainActions.js +++ b/com.twin.app.shoptime/src/actions/mainActions.js @@ -1,5 +1,5 @@ import { URLS } from '../api/apiConfig'; -import { TAxios } from '../api/TAxios'; +import { TAxios, TAxiosAdvancedPromise } from '../api/TAxios'; import { convertUtcToLocal } from '../components/MediaPlayer/util'; import { CATEGORY_DATA_MAX_RESULTS_LIMIT, LOG_CONTEXT_NAME, LOG_MESSAGE_ID } from '../utils/Config'; import * as HelperMethods from '../utils/helperMethods'; @@ -464,29 +464,29 @@ export const getHomeFullVideoInfo = export const getMainLiveShowNowProduct = ({ patnrId, showId, lstChgDt }) => (dispatch, getState) => { - const onSuccess = (response) => { - // console.log('getMainLiveShowNowProduct onSuccess', response.data); - - dispatch({ - type: types.GET_MAIN_LIVE_SHOW_NOW_PRODUCT, - payload: response.data.data, - }); - }; - - const onFail = (error) => { - console.error('getMainLiveShowNowProduct onFail', error); - }; - - TAxios( + return TAxiosAdvancedPromise( dispatch, getState, 'get', URLS.GET_MAIN_LIVE_SHOW_NOW_PRODUCT, { patnrId, showId, lstChgDt }, {}, - onSuccess, - onFail - ); + { + retries: 2, // 3ํšŒ๊นŒ์ง€ ์žฌ์‹œ๋„ (์ฒ˜์Œ ์‹œ๋„ + 2ํšŒ ์žฌ์‹œ๋„) + retryDelay: 500, // 500ms ๊ฐ„๊ฒฉ์œผ๋กœ ์žฌ์‹œ๋„ + throwOnError: false, // ์—๋Ÿฌ๋ฅผ throwํ•˜์ง€ ์•Š๊ณ  ๊ฐ์ฒด๋กœ ๋ฐ˜ํ™˜ + } + ).then((result) => { + if (result.success && result.data?.data) { + dispatch({ + type: types.GET_MAIN_LIVE_SHOW_NOW_PRODUCT, + payload: result.data.data, + }); + } else { + console.error('getMainLiveShowNowProduct onFail', result.error); + } + return result; + }); }; export const clearShopNowInfo = () => { diff --git a/com.twin.app.shoptime/src/actions/playActions.js b/com.twin.app.shoptime/src/actions/playActions.js index 9e902a26..ca35d97c 100644 --- a/com.twin.app.shoptime/src/actions/playActions.js +++ b/com.twin.app.shoptime/src/actions/playActions.js @@ -68,24 +68,43 @@ export const startVideoPlayer = ...rest }) => (dispatch, getState) => { - console.log('[startVideoPlayer] โœ… START - videoId:', videoId, ', showUrl:', showUrl, ', modal:', modal); + console.log( + '[startVideoPlayer] โœ… START - videoId:', + videoId, + ', showUrl:', + showUrl, + ', modal:', + modal + ); // ๐Ÿ”ฝ [251116] ์ฆ‰์‹œ ๋กœ๋”ฉ ์ƒํƒœ ์„ค์ • const videoIdentifier = videoId || showUrl; if (videoIdentifier) { const displayMode = modal ? DISPLAY_STATUS.VISIBLE : DISPLAY_STATUS.FULLSCREEN; - console.log('[startVideoPlayer] ๐Ÿ“Œ Setting playback loading - identifier:', videoIdentifier, ', displayMode:', displayMode); + console.log( + '[startVideoPlayer] ๐Ÿ“Œ Setting playback loading - identifier:', + videoIdentifier, + ', displayMode:', + displayMode + ); dispatch(setPlaybackLoading(videoIdentifier, displayMode)); } else { - console.log('[startVideoPlayer] โš ๏ธ No videoIdentifier provided (videoId and showUrl are both missing)'); + console.log( + '[startVideoPlayer] โš ๏ธ No videoIdentifier provided (videoId and showUrl are both missing)' + ); } const panels = getState().panels.panels; const topPanel = panels[panels.length - 1]; let panelWorkingAction = pushPanel; - + const panelName = panel_names.PLAYER_PANEL; - console.log('[startVideoPlayer] ๐Ÿ“Š Panel state - panelsCount:', panels.length, ', topPanelName:', topPanel?.name); + console.log( + '[startVideoPlayer] ๐Ÿ“Š Panel state - panelsCount:', + panels.length, + ', topPanelName:', + topPanel?.name + ); if (topPanel && topPanel.name === panelName) { panelWorkingAction = updatePanel; @@ -161,13 +180,27 @@ export const startVideoPlayerNew = ...rest }) => (dispatch, getState) => { - console.log('[startVideoPlayerNew] *** โœ… START - bannerId:', bannerId, ', videoId:', videoId, ', showUrl:', showUrl, ', modal:', modal); + console.log( + '[startVideoPlayerNew] *** โœ… START - bannerId:', + bannerId, + ', videoId:', + videoId, + ', showUrl:', + showUrl, + ', modal:', + modal + ); // ๐Ÿ”ฝ [251116] ์ฆ‰์‹œ ๋กœ๋”ฉ ์ƒํƒœ ์„ค์ • const videoIdentifier = videoId || showUrl || bannerId; if (videoIdentifier) { const displayMode = modal ? DISPLAY_STATUS.VISIBLE : DISPLAY_STATUS.FULLSCREEN; - console.log('[startVideoPlayerNew] *** ๐Ÿ“Œ Setting playback loading - identifier:', videoIdentifier, ', displayMode:', displayMode); + console.log( + '[startVideoPlayerNew] *** ๐Ÿ“Œ Setting playback loading - identifier:', + videoIdentifier, + ', displayMode:', + displayMode + ); dispatch(setPlaybackLoading(videoIdentifier, displayMode)); } else { console.log('[startVideoPlayerNew] *** โš ๏ธ No videoIdentifier provided'); @@ -179,11 +212,19 @@ export const startVideoPlayerNew = // const panelName = useNewPlayer ? panel_names.PLAYER_PANEL_NEW : panel_names.PLAYER_PANEL; const panelName = panel_names.PLAYER_PANEL; - console.log('[startVideoPlayerNew] *** ๐Ÿ“Š Panel state - panelsCount:', panels.length, ', topPanelName:', topPanel?.name); + console.log( + '[startVideoPlayerNew] *** ๐Ÿ“Š Panel state - panelsCount:', + panels.length, + ', topPanelName:', + topPanel?.name + ); if (topPanel && topPanel.name === panelName) { panelWorkingAction = updatePanel; - console.log('[startVideoPlayerNew] *** ๐Ÿ“‹ Current PLAYER_PANEL panelInfo:', topPanel.panelInfo); + console.log( + '[startVideoPlayerNew] *** ๐Ÿ“‹ Current PLAYER_PANEL panelInfo:', + topPanel.panelInfo + ); } // ์ค‘๋ณต ์‹คํ–‰ ๋ฐฉ์ง€: ๊ฐ™์€ ๋ฐฐ๋„ˆ + ๊ฐ™์€ modal ์ƒํƒœ/์ปจํ…Œ์ด๋„ˆ + ๊ฐ™์€ URL์ด๋ฉด skip @@ -195,7 +236,18 @@ export const startVideoPlayerNew = const isSameShowUrl = currentPanelInfo.showUrl === showUrl; const isSameVideoId = currentPanelInfo.videoId === videoId; - console.log('[startVideoPlayerNew] *** ๐Ÿ” Duplicate check - isSameBanner:', isSameBanner, ', isSameModalType:', isSameModalType, ', isSameContainer:', isSameContainer, ', isSameShowUrl:', isSameShowUrl, ', isSameVideoId:', isSameVideoId); + console.log( + '[startVideoPlayerNew] *** ๐Ÿ” Duplicate check - isSameBanner:', + isSameBanner, + ', isSameModalType:', + isSameModalType, + ', isSameContainer:', + isSameContainer, + ', isSameShowUrl:', + isSameShowUrl, + ', isSameVideoId:', + isSameVideoId + ); if (isSameBanner && isSameModalType && isSameContainer && isSameShowUrl && isSameVideoId) { console.log('[startVideoPlayerNew] *** โญ๏ธ SKIPPED - ๋™์ผํ•œ ์š”์ฒญ', { @@ -232,7 +284,10 @@ export const startVideoPlayerNew = true ) ); - console.log('[startVideoPlayerNew] *** โœจ Panel action dispatched - action:', panelWorkingAction === updatePanel ? 'updatePanel' : 'pushPanel'); + console.log( + '[startVideoPlayerNew] *** โœจ Panel action dispatched - action:', + panelWorkingAction === updatePanel ? 'updatePanel' : 'pushPanel' + ); // [COMMENTED OUT] ๋น„๋””์˜ค ์žฌ์ƒ ์‹œ ๊ฐ•์ œ ํฌ์ปค์Šค ์ด๋™ ๋น„ํ™œ์„ฑํ™” // if (modal && modalContainerId && !spotlightDisable) { @@ -324,7 +379,7 @@ export const pauseModalVideo = () => (dispatch, getState) => { // ๋ชจ๋‹ฌ ๋น„๋””์˜ค๋ฅผ ์žฌ์ƒ (์ผ์‹œ์ •์ง€ ํ•ด์ œ) export const resumeModalVideo = () => (dispatch, getState) => { const panels = getState().panels.panels; - + // modal PlayerPanel ์ฐพ๊ธฐ const modalPlayerPanel = panels.find( (panel) => panel.name === panel_names.PLAYER_PANEL && panel.panelInfo?.modal @@ -498,7 +553,10 @@ export const showModalVideo = () => (dispatch, getState) => { skipModalStyleRecalculation: false, // ์œ„์น˜ ๋ณ€๊ฒฝ ์‹œ DOM ๊ธฐ์ค€์œผ๋กœ ๋‹ค์‹œ ๊ณ„์‚ฐํ•˜๋„๋ก ํ—ˆ์šฉ }; - console.log('[showModalVideo] *** ๐Ÿ”„ Updated panelInfo - shouldShrinkTo1px:', updatedPanelInfo.shouldShrinkTo1px); + console.log( + '[showModalVideo] *** ๐Ÿ”„ Updated panelInfo - shouldShrinkTo1px:', + updatedPanelInfo.shouldShrinkTo1px + ); console.log('[showModalVideo] *** ๐Ÿ“ Restored modalStyle:', updatedPanelInfo.modalStyle); dispatch( @@ -699,10 +757,27 @@ export const CLEAR_PLAYER_INFO = () => ({ * @param {number} playState.duration - ์ „์ฒด ๋น„๋””์˜ค ๊ธธ์ด(์ดˆ) * @param {number} playState.playbackRate - ์žฌ์ƒ ์†๋„ */ -export const updateVideoPlayState = (playState) => ({ - type: types.UPDATE_VIDEO_PLAY_STATE, - payload: playState, -}); +export const updateVideoPlayState = (playState) => (dispatch, getState) => { + const currentState = getState().play.videoPlayState; + + // ์ƒํƒœ ๋ณ€ํ™” ๊ฐ์ง€ + const hasChanges = Object.keys(playState).some((key) => { + return currentState[key] !== playState[key]; + }); + + if (hasChanges) { + console.log('๐Ÿ”„ [Redux] updateVideoPlayState action created', { + ...playState, + timestamp: new Date().toISOString(), + caller: new Error().stack?.split('\n')[2]?.trim() || 'unknown', + }); + } + + dispatch({ + type: types.UPDATE_VIDEO_PLAY_STATE, + payload: playState, + }); +}; /* ๐Ÿ”ฝ [์ถ”๊ฐ€] ์ƒˆ๋กœ์šด 'ํ”Œ๋ ˆ์ด ์ œ์–ด ๋งค๋‹ˆ์ €' ์•ก์…˜๋“ค */ diff --git a/com.twin.app.shoptime/src/api/TAxios.js b/com.twin.app.shoptime/src/api/TAxios.js index 9cda4614..b7b68f47 100644 --- a/com.twin.app.shoptime/src/api/TAxios.js +++ b/com.twin.app.shoptime/src/api/TAxios.js @@ -29,7 +29,7 @@ export const setTokenRefreshing = (value) => { tokenRefreshing = value; }; export const runDelayedAction = (dispatch, getState) => { - console.log('runDelayedAction axiosQueue size', axiosQueue.length); + // console.log('runDelayedAction axiosQueue size', axiosQueue.length); while (axiosQueue.length > 0) { const requestConfig = axiosQueue.shift(); // queue์—์„œ ์š”์ฒญ์„ ํ•˜๋‚˜์”ฉ shift TAxios( @@ -309,7 +309,7 @@ export const TAxiosAdvancedPromise = ( const attemptRequest = () => { attempts++; - console.log(`TAxiosPromise attempt ${attempts}/${maxAttempts} for ${baseUrl}`); + // console.log(`TAxiosPromise attempt ${attempts}/${maxAttempts} for ${baseUrl}`); const timeoutId = setTimeout(() => { const timeoutError = new Error(`Request timeout after ${timeout}ms for ${baseUrl}`); @@ -335,7 +335,7 @@ export const TAxiosAdvancedPromise = ( // onSuccess (response) => { clearTimeout(timeoutId); - console.log(`TAxiosPromise success on attempt ${attempts} for ${baseUrl}`); + // console.log(`TAxiosPromise success on attempt ${attempts} for ${baseUrl}`); resolve({ success: true, data: response.data, @@ -491,7 +491,7 @@ export const safeUsageExamples = { }); if (result.success) { - console.log('Success:', result.data); + // console.log('Success:', result.data); return result.data; } else { console.error('API call failed:', result.error); @@ -534,7 +534,7 @@ export const safeUsageExamples = { const result = await TAxiosAll(requests); if (result.success) { - console.log('All requests succeeded'); + // console.log('All requests succeeded'); return result.successResults.map((item) => item.result); } else { console.error('Some requests failed:', result.failedResults); @@ -562,7 +562,7 @@ export const ComponentUsageExample = () => { setLoading(false); if (result.success) { - console.log('Terms fetched successfully'); + // console.log('Terms fetched successfully'); // ์„ฑ๊ณต ์ฒ˜๋ฆฌ (์˜ˆ: ์„ฑ๊ณต ํ† ์ŠคํŠธ ํ‘œ์‹œ) } else { console.error('Failed to fetch terms:', result.message); diff --git a/com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.js b/com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.js index c03e58ad..ef01c6c9 100644 --- a/com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.js +++ b/com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.js @@ -725,6 +725,7 @@ const VideoPlayerBase = class extends React.Component { tabContainerVersion: PropTypes.number, tabIndexV2: PropTypes.number, dispatch: PropTypes.func, + videoPlayState: PropTypes.object, }; static contextType = FloatingLayerContext; @@ -1546,17 +1547,101 @@ const VideoPlayerBase = class extends React.Component { } this.setState(updatedState); - // Redux์— ๋น„๋””์˜ค ์žฌ์ƒ ์ƒํƒœ ์—…๋ฐ์ดํŠธ + // Redux์— ๋น„๋””์˜ค ์žฌ์ƒ ์ƒํƒœ ์—…๋ฐ์ดํŠธ (๊ธฐ์กด ๋กœ์ง ์œ ์ง€) if (this.props.dispatch) { - this.props.dispatch( - updateVideoPlayState({ + // ๐Ÿ”ฅ onProgress ์ด๋ฒคํŠธ๋Š” Redux ์—…๋ฐ์ดํŠธํ•˜์ง€ ์•Š์Œ (๋นˆ๋ฒˆํ•œ ์ด๋ฒคํŠธ) + const shouldUpdateRedux = !['onProgress'].includes(ev.type); + + if (shouldUpdateRedux) { + const updateState = { isPlaying: !updatedState.paused, isPaused: updatedState.paused, currentTime: updatedState.currentTime, duration: updatedState.duration, playbackRate: updatedState.playbackRate, - }) - ); + }; + + // ๊ฐ€์žฅ ์ค‘์š”ํ•œ ์ด๋ฒคํŠธ๋งŒ ๋กœ๊ทธ + const shouldLogEvent = ['play', 'pause', 'ended'].includes(ev.type); + if (shouldLogEvent) { + console.log('๐Ÿ”„ [PlayerPanel][VideoPlayer] Event-driven Redux update', { + eventType: ev.type, + videoState: updatedState, + updateState, + timestamp: new Date().toISOString(), + }); + } + + // ๐Ÿ” Redux dispatch ํ™•์ธ + console.log('๐Ÿ“ค [PlayerPanel][VideoPlayer] Dispatching Redux update', { + eventType: ev.type, + updateState, + hasDispatch: !!this.props.dispatch, + propsVideoPlayState: this.props.videoPlayState, + }); + + this.props.dispatch(updateVideoPlayState(updateState)); + } + } else { + console.log('โŒ [PlayerPanel][VideoPlayer] No dispatch prop available', { + props: Object.keys(this.props), + hasDispatch: !!this.props.dispatch, + hasVideoPlayState: !!this.props.videoPlayState, + }); + } + + // ๐Ÿ”น [๊ฐ•ํ™”] ๋‚ด๋ถ€ ์ƒํƒœ์™€ Redux ์ƒํƒœ ๋™๊ธฐํ™” + // Redux ์ƒํƒœ๋ฅผ ์šฐ์„ ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜์—ฌ ๋‚ด๋ถ€ ์ƒํƒœ ์ผ๊ด€์„ฑ ํ™•๋ณด + if (this.props.videoPlayState && typeof this.props.videoPlayState === 'object') { + // Redux ์ƒํƒœ ๋””๋ฒ„๊น… (์ตœ์†Œํ•œ์˜ ์ค‘์š” ์ด๋ฒคํŠธ๋งŒ) + if (ev.type === 'play' || ev.type === 'pause') { + console.log('๐Ÿ” [PlayerPanel][VideoPlayer] Redux state debug', { + videoPlayState: this.props.videoPlayState, + isPaused: this.props.videoPlayState?.isPaused, + isPlaying: this.props.videoPlayState?.isPlaying, + currentTime: this.props.videoPlayState?.currentTime, + eventType: ev.type, + timestamp: new Date().toISOString(), + }); + } + const { currentTime, paused, playbackRate } = this.props.videoPlayState; + + // Redux ์ƒํƒœ์™€ ํ˜„์žฌ ๋‚ด๋ถ€ ์ƒํƒœ๊ฐ€ ํฌ๊ฒŒ ๋‹ค๋ฅผ ๊ฒฝ์šฐ ๋‚ด๋ถ€ ์ƒํƒœ ์—…๋ฐ์ดํŠธ + const timeDiff = Math.abs(currentTime - this.state.currentTime); + const shouldUpdateTime = timeDiff > 0.5; // 0.5์ดˆ ์ด์ƒ ์ฐจ์ด ์‹œ ์—…๋ฐ์ดํŠธ + + // ๋นˆ๋ฒˆํ•œ ์ด๋ฒคํŠธ๋Š” ๋กœ๊ทธ์—์„œ ์ œ์™ธ + const isFrequentEvent = [ + 'onProgress', + 'onBuffer', + 'onBufferEnd', + 'onReady', + 'onDuration', + 'onStart', + ].includes(ev.type); + const hasSignificantChange = + shouldUpdateTime || (paused !== this.state.paused && !isFrequentEvent); + + // ์ค‘์š”ํ•œ ์ƒํƒœ ๋ณ€ํ™”๊ฐ€ ์žˆ๊ณ  ๋นˆ๋ฒˆํ•œ ์ด๋ฒคํŠธ๊ฐ€ ์•„๋‹ ๋•Œ๋งŒ ๋กœ๊ทธ + if (hasSignificantChange && !isFrequentEvent) { + console.log('๐Ÿ”„ [PlayerPanel][VideoPlayer] Syncing internal state with Redux', { + timeDiff, + shouldUpdateTime, + pausedDiff: paused !== this.state.paused, + reduxPaused: paused, + internalPaused: this.state.paused, + eventType: ev.type, + timestamp: new Date().toISOString(), + }); + } + + if (hasSignificantChange) { + this.setState({ + currentTime: shouldUpdateTime ? currentTime : this.state.currentTime, + paused: paused !== undefined ? paused : this.state.paused, + playbackRate: playbackRate !== undefined ? playbackRate : this.state.playbackRate, + }); + } } }; @@ -1569,6 +1654,7 @@ const VideoPlayerBase = class extends React.Component { /** * Returns an object with the current state of the media including `currentTime`, `duration`, * `paused`, `playbackRate`, `proportionLoaded`, and `proportionPlayed`. + * Redux ์ƒํƒœ์™€ ๋‚ด๋ถ€ ์ƒํƒœ๋ฅผ ์šฐ์„ ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜์—ฌ ์ผ๊ด€์„ฑ ๋ณด์žฅ * * @function * @memberof sandstone/VideoPlayer.VideoPlayerBase.prototype @@ -1576,13 +1662,19 @@ const VideoPlayerBase = class extends React.Component { * @public */ getMediaState = () => { + // Redux ์ƒํƒœ๋ฅผ ์šฐ์„ ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜์—ฌ ์ผ๊ด€์„ฑ ๋ณด์žฅ + // Redux ์ƒํƒœ๊ฐ€ ์—†์œผ๋ฉด ๋‚ด๋ถ€ ์ƒํƒœ ์‚ฌ์šฉ (fallback) + const reduxState = this.props.videoPlayState; + return { - currentTime: this.state.currentTime, - duration: this.state.duration, - paused: this.state.paused, - playbackRate: this.video?.playbackRate, + currentTime: reduxState?.currentTime ?? this.state.currentTime, + duration: reduxState?.duration ?? this.state.duration, + paused: reduxState?.isPaused ?? this.state.paused, + playbackRate: reduxState?.playbackRate ?? this.video?.playbackRate ?? this.state.playbackRate, proportionLoaded: this.state.proportionLoaded, proportionPlayed: this.state.proportionPlayed, + // Redux ์ƒํƒœ ์ •๋ณด๋„ ํฌํ•จ + isPlaying: reduxState?.isPlaying ?? !this.state.paused, }; }; @@ -1611,7 +1703,16 @@ const VideoPlayerBase = class extends React.Component { * @public */ play = () => { + console.log('๐ŸŸข [PlayerPanel][VideoPlayer] play() called', { + currentTime: this.state.currentTime, + duration: this.state.duration, + paused: this.state.paused, + sourceUnavailable: this.state.sourceUnavailable, + prevCommand: this.prevCommand, + }); + if (this.state.sourceUnavailable) { + console.log('โš ๏ธ [PlayerPanel][VideoPlayer] play() aborted - source unavailable'); return; } @@ -1623,6 +1724,19 @@ const VideoPlayerBase = class extends React.Component { this.send('play'); this.announce($L('Play')); this.startDelayedMiniFeedbackHide(5000); + + // Redux ์ƒํƒœ ์—…๋ฐ์ดํŠธ - ์žฌ์ƒ ์ƒํƒœ๋กœ ๋ณ€๊ฒฝ + if (this.props.dispatch) { + this.props.dispatch( + updateVideoPlayState({ + isPlaying: true, + isPaused: false, + currentTime: this.state.currentTime, + duration: this.state.duration, + playbackRate: 1, + }) + ); + } }; /** @@ -1633,7 +1747,16 @@ const VideoPlayerBase = class extends React.Component { * @public */ pause = () => { + console.log('๐Ÿ”ด [VideoPlayer] pause() called', { + currentTime: this.state.currentTime, + duration: this.state.duration, + paused: this.state.paused, + sourceUnavailable: this.state.sourceUnavailable, + prevCommand: this.prevCommand, + }); + if (this.state.sourceUnavailable) { + console.log('โš ๏ธ [VideoPlayer] pause() aborted - source unavailable'); return; } @@ -1645,6 +1768,22 @@ const VideoPlayerBase = class extends React.Component { this.send('pause'); this.announce($L('Pause')); this.stopDelayedMiniFeedbackHide(); + + // Redux ์ƒํƒœ ์—…๋ฐ์ดํŠธ - ์ผ์‹œ์ •์ง€ ์ƒํƒœ๋กœ ๋ณ€๊ฒฝ + if (this.props.dispatch) { + const pauseState = { + isPlaying: false, + isPaused: true, + currentTime: this.state.currentTime, + duration: this.state.duration, + playbackRate: 1, + }; + + console.log('๐Ÿ“ค [VideoPlayer] Dispatching pause state', pauseState); + this.props.dispatch(updateVideoPlayState(pauseState)); + } else { + console.log('โš ๏ธ [VideoPlayer] No dispatch prop available - Redux state not updated'); + } }; /** @@ -1656,6 +1795,15 @@ const VideoPlayerBase = class extends React.Component { * @public */ seek = (timeIndex) => { + console.log('โฉ [VideoPlayer] seek() called', { + timeIndex, + currentTime: this.state.currentTime, + duration: this.state.duration, + videoDuration: this.video?.duration, + seekDisabled: this.props.seekDisabled, + sourceUnavailable: this.state.sourceUnavailable, + }); + if (this.video) { if ( !this.props.seekDisabled && @@ -1663,14 +1811,37 @@ const VideoPlayerBase = class extends React.Component { !this.state.sourceUnavailable ) { // last time error - if (timeIndex >= this.video.duration) { - this.video.currentTime = this.video.duration - 1; + const actualSeekTime = + timeIndex >= this.video.duration ? this.video.duration - 1 : timeIndex; + this.video.currentTime = actualSeekTime; + + console.log('โฉ [VideoPlayer] Video seek completed', { + requestedTime: timeIndex, + actualTime: actualSeekTime, + videoDuration: this.video.duration, + }); + + // Redux ์ƒํƒœ ์—…๋ฐ์ดํŠธ - ์‹œ๊ฐ„ ์ด๋™ ์ƒํƒœ ๋ฐ˜์˜ + if (this.props.dispatch) { + const seekState = { + isPlaying: !this.state.paused, + isPaused: this.state.paused, + currentTime: actualSeekTime, + duration: this.state.duration, + playbackRate: this.state.playbackRate, + }; + + console.log('๐Ÿ“ค [VideoPlayer] Dispatching seek state', seekState); + this.props.dispatch(updateVideoPlayState(seekState)); } else { - this.video.currentTime = timeIndex; + console.log('โš ๏ธ [VideoPlayer] No dispatch prop available - Redux state not updated'); } } else { + console.log('โŒ [VideoPlayer] seek failed - disabled or source unavailable'); forward('onSeekFailed', {}, this.props); } + } else { + console.log('โŒ [VideoPlayer] seek failed - no video element'); } }; diff --git a/com.twin.app.shoptime/src/reducers/playReducer.js b/com.twin.app.shoptime/src/reducers/playReducer.js index 62e62914..93c6aea7 100644 --- a/com.twin.app.shoptime/src/reducers/playReducer.js +++ b/com.twin.app.shoptime/src/reducers/playReducer.js @@ -4,21 +4,52 @@ import { PLAYBACK_STATUS, DISPLAY_STATUS } from '../actions/playActions'; const initialState = { subTitleBlobs: {}, chatData: null, - videoPlayState: { - // ๊ธฐ์กด ์ƒํƒœ๋“ค ์œ ์ง€ + // ๐Ÿ” ํŒจ๋„๋ณ„ ๋น„๋””์˜ค ์ƒํƒœ ๋ถ„๋ฆฌ + playerPanelVideoState: { + // PlayerPanel ์ „์šฉ ์ƒํƒœ isPlaying: false, isPaused: true, currentTime: 0, duration: 0, playbackRate: 1, - // ๐Ÿ”ฝ [251116] ์ƒˆ๋กœ์šด ๋น„๋””์˜ค ์ƒํƒœ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ - playback: PLAYBACK_STATUS.NOT_PLAYING, // ์žฌ์ƒ ์ƒํƒœ - display: DISPLAY_STATUS.HIDDEN, // ํ™”๋ฉด ํ‘œ์‹œ ์ƒํƒœ - videoId: null, // ํ˜„์žฌ ๋น„๋””์˜ค ID - loadingProgress: 0, // ๋กœ๋”ฉ ์ง„ํ–‰๋ฅ  (0-100) - loadingError: null, // ๋กœ๋”ฉ ์—๋Ÿฌ ์ •๋ณด - lastUpdate: null, // ๋งˆ์ง€๋ง‰ ์—…๋ฐ์ดํŠธ ์‹œ๊ฐ„ + // [251116] ์ƒˆ๋กœ์šด ๋น„๋””์˜ค ์ƒํƒœ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ + playback: PLAYBACK_STATUS.NOT_PLAYING, + display: DISPLAY_STATUS.HIDDEN, + videoId: null, + loadingProgress: 0, + loadingError: null, + lastUpdate: null, + }, + detailPanelVideoState: { + // DetailPanel ์ „์šฉ ์ƒํƒœ + isPlaying: false, + isPaused: true, + currentTime: 0, + duration: 0, + playbackRate: 1, + + // [251116] ์ƒˆ๋กœ์šด ๋น„๋””์˜ค ์ƒํƒœ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ + playback: PLAYBACK_STATUS.NOT_PLAYING, + display: DISPLAY_STATUS.HIDDEN, + videoId: null, + loadingProgress: 0, + loadingError: null, + lastUpdate: null, + }, + // ๊ธฐ์กด videoPlayState๋Š” ํ•˜์œ„ ํ˜ธํ™˜์„ฑ์„ ์œ„ํ•ด ์œ ์ง€ + videoPlayState: { + isPlaying: false, + isPaused: true, + currentTime: 0, + duration: 0, + playbackRate: 1, + playback: PLAYBACK_STATUS.NOT_PLAYING, + display: DISPLAY_STATUS.HIDDEN, + videoId: null, + loadingProgress: 0, + loadingError: null, + lastUpdate: null, }, }; @@ -60,12 +91,35 @@ export const playReducer = (state = initialState, action) => { }; } case types.UPDATE_VIDEO_PLAY_STATE: { + const newState = { + ...state.videoPlayState, + ...action.payload, + }; + + // ๐Ÿ” ์‹ค์ œ ์ƒํƒœ ๋ณ€ํ™” ๊ฐ์ง€ - ์ค‘์š”ํ•œ ๋ณ€ํ™”๋งŒ ๋กœ๊น… + const importantKeys = ['isPaused', 'isPlaying', 'playback', 'display']; + const hasImportantChange = importantKeys.some((key) => { + return state.videoPlayState?.[key] !== newState[key]; + }); + + if (hasImportantChange) { + console.log('๐Ÿ”„ [Redux Reducer] VIDEO PLAY STATE CRITICAL CHANGE', { + previousPaused: state.videoPlayState?.isPaused, + newPaused: newState.isPaused, + previousPlaying: state.videoPlayState?.isPlaying, + newPlaying: newState.isPlaying, + previousPlayback: state.videoPlayState?.playback, + newPlayback: newState.playback, + previousDisplay: state.videoPlayState?.display, + newDisplay: newState.display, + currentTime: newState.currentTime, + timestamp: new Date().toISOString(), + }); + } + return { ...state, - videoPlayState: { - ...state.videoPlayState, - ...action.payload, - }, + videoPlayState: newState, }; } diff --git a/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx b/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx index 5a6396a2..1238ae6e 100644 --- a/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx +++ b/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx @@ -20,7 +20,7 @@ import liveShow from '../../../../assets/images/tag-liveshow.png'; import { changeAppStatus } from '../../../actions/commonActions'; import { updateHomeInfo, setVideoTransitionLock } from '../../../actions/homeActions'; import { sendLogTopContents, sendLogTotalRecommend } from '../../../actions/logActions'; -import { pushPanel, navigateFromRandomUnit } from '../../../actions/panelActions'; +import { pushPanel, navigateFromRandomUnit, SOURCE_MENUS } from '../../../actions/panelActions'; import { finishVideoPreview, startVideoPlayer, @@ -451,6 +451,8 @@ export default function RandomUnit({ patnrId: randomData.patnrId, prdtId: randomData.prdtId, curationId: randomData.lnkCurationId, + sourcePanel: panel_names.HOME_PANEL, + sourceMenu: SOURCE_MENUS.HOME_RANDOM_UNIT, }, }; break; @@ -473,6 +475,8 @@ export default function RandomUnit({ curationId: randomData.lnkCurationId, prdtId: randomData.prdtId, type: 'theme', + sourcePanel: panel_names.HOME_PANEL, + sourceMenu: SOURCE_MENUS.HOME_RANDOM_UNIT, }, }; break; diff --git a/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RollingUnit.jsx b/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RollingUnit.jsx index f5884281..64022074 100644 --- a/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RollingUnit.jsx +++ b/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RollingUnit.jsx @@ -1,42 +1,22 @@ -import React, { - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import classNames from 'classnames'; -import { - useDispatch, - useSelector, -} from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import Spotlight from '@enact/spotlight'; -import SpotlightContainerDecorator - from '@enact/spotlight/SpotlightContainerDecorator'; +import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator'; import Spottable from '@enact/spotlight/Spottable'; import { getContainerId } from '@enact/spotlight/src/container'; import btnPlay from '../../../../assets/images/btn/btn-play-thumb-nor.png'; -import defaultLogoImg - from '../../../../assets/images/ic-tab-partners-default@3x.png'; -import emptyHorImage - from '../../../../assets/images/img-home-banner-empty-hor.png'; -import emptyVerImage - from '../../../../assets/images/img-home-banner-empty-ver.png'; -import defaultImageItem - from '../../../../assets/images/img-thumb-empty-product@3x.png'; +import defaultLogoImg from '../../../../assets/images/ic-tab-partners-default@3x.png'; +import emptyHorImage from '../../../../assets/images/img-home-banner-empty-hor.png'; +import emptyVerImage from '../../../../assets/images/img-home-banner-empty-ver.png'; +import defaultImageItem from '../../../../assets/images/img-thumb-empty-product@3x.png'; import liveShow from '../../../../assets/images/tag-liveshow.png'; -import { - setBannerIndex, - updateHomeInfo, -} from '../../../actions/homeActions'; -import { - sendLogTopContents, - sendLogTotalRecommend, -} from '../../../actions/logActions'; -import { pushPanel } from '../../../actions/panelActions'; +import { setBannerIndex, updateHomeInfo } from '../../../actions/homeActions'; +import { sendLogTopContents, sendLogTotalRecommend } from '../../../actions/logActions'; +import { pushPanel, SOURCE_MENUS } from '../../../actions/panelActions'; import { startVideoPlayer, finishVideoPreview } from '../../../actions/playActions'; import CustomImage from '../../../components/CustomImage/CustomImage'; import usePriceInfo from '../../../hooks/usePriceInfo'; @@ -47,31 +27,25 @@ import { LOG_TP_NO, panel_names, } from '../../../utils/Config'; -import { - $L, - formatGMTString, -} from '../../../utils/helperMethods'; +import { $L, formatGMTString } from '../../../utils/helperMethods'; import { TEMPLATE_CODE_CONF } from '../HomePanel'; import css from './RollingUnit.module.less'; -const SpottableComponent = Spottable("div"); +const SpottableComponent = Spottable('div'); -const Container = SpotlightContainerDecorator( - { enterTo: "last-focused", preserveId: true }, - "div" -); +const Container = SpotlightContainerDecorator({ enterTo: 'last-focused', preserveId: true }, 'div'); const LINK_TYPES = { - FEATURED_BRANDS: "DSP00501", - TRENDING_NOW: "DSP00502", - HOT_PICKS: "DSP00503", - ON_SALE: "DSP00504", - CATEGORY: "DSP00505", - PRODUCT_DETAIL: "DSP00506", - VOD: "DSP00507", - SHOW_DETAIL: "DSP00508", - THEME: "DSP00509", - JUSTFORYOU: "DSP00510", + FEATURED_BRANDS: 'DSP00501', + TRENDING_NOW: 'DSP00502', + HOT_PICKS: 'DSP00503', + ON_SALE: 'DSP00504', + CATEGORY: 'DSP00505', + PRODUCT_DETAIL: 'DSP00506', + VOD: 'DSP00507', + SHOW_DETAIL: 'DSP00508', + THEME: 'DSP00509', + JUSTFORYOU: 'DSP00510', }; const createPanelInfo = (data, categoryData = {}) => ({ @@ -101,17 +75,11 @@ export default function RollingUnit({ const { curationId, curationTitle } = useSelector((state) => state.home); const curtNm = useSelector((state) => state.home?.bannerData?.curtNm); - const shptmTmplCd = useSelector( - (state) => state.home?.bannerData?.shptmTmplCd - ); + const shptmTmplCd = useSelector((state) => state.home?.bannerData?.shptmTmplCd); const nowMenu = useSelector((state) => state.common.menu.nowMenu); const entryMenu = useSelector((state) => state.common.menu.entryMenu); - const introTermsAgree = useSelector( - (state) => state.common.optionalTermsAgree - ); - const homeCategory = useSelector( - (state) => state.home.menuData?.data?.homeCategory - ); + const introTermsAgree = useSelector((state) => state.common.optionalTermsAgree); + const homeCategory = useSelector((state) => state.home.menuData?.data?.homeCategory); const countryCode = useSelector((state) => state.common.httpHeader.cntry_cd); const { userNumber } = useSelector((state) => state.common.appStatus.loginUserData); @@ -127,15 +95,15 @@ export default function RollingUnit({ // ์ปดํฌ๋„ŒํŠธ ์ƒ๋‹จ์—์„œ ํ•„ํ„ฐ๋ง const filteredRollingData = useMemo(() => { return rollingData.filter( - (item) => (introTermsAgree === true && (userNumber !== undefined && userNumber !== '')) || item.shptmLnkTpCd !== "DSP00510" + (item) => + (introTermsAgree === true && userNumber !== undefined && userNumber !== '') || + item.shptmLnkTpCd !== 'DSP00510' ); }, [rollingData, introTermsAgree, userNumber]); // ์ดํ›„ filteredRollingData ์‚ฌ์šฉ const rollingDataLength = filteredRollingData.length; - const [startIndex, setStartIndex] = useState( - savedIndex !== undefined ? savedIndex : 0 - ); + const [startIndex, setStartIndex] = useState(savedIndex !== undefined ? savedIndex : 0); const lastIndexRef = useRef(rollingDataLength - 1); const doRollingRef = useRef(false); const [unitHasFocus, setUnitHasFocus] = useState(false); @@ -157,14 +125,14 @@ export default function RollingUnit({ switch (currentRollingData?.shptmBanrTpCd) { // case: "LIVE" or "VOD" - case "DSP00301": - case "DSP00302": + case 'DSP00301': + case 'DSP00302': contId = currentRollingData?.showId; contNm = currentRollingData?.showNm; break; // case: "Image Banner" - case "DSP00303": + case 'DSP00303': contId = currentRollingData?.shptmLnkTpCd; contNm = currentRollingData?.shptmLnkTpNm; break; @@ -177,27 +145,27 @@ export default function RollingUnit({ } if ( - currentRollingData?.shptmLnkTpCd === "DSP00503" || // "Hot Picks" - currentRollingData?.shptmLnkTpCd === "DSP00509" // "Theme" + currentRollingData?.shptmLnkTpCd === 'DSP00503' || // "Hot Picks" + currentRollingData?.shptmLnkTpCd === 'DSP00509' // "Theme" ) { - contNm = contNm + " | " + currentRollingData?.lnkCurationId; + contNm = contNm + ' | ' + currentRollingData?.lnkCurationId; } return { banrNo: `${currentRollingData?.banrDpOrd}`, banrTpNm: currentRollingData?.vtctpYn - ? currentRollingData.vtctpYn === "Y" - ? "Vertical" - : "Horizontal" - : "", + ? currentRollingData.vtctpYn === 'Y' + ? 'Vertical' + : 'Horizontal' + : '', contId, contNm, - contTpNm: currentRollingData?.shptmBanrTpNm ?? "", - dspyTpNm: bannerDataRef.current?.shptmDspyTpNm ?? "", - expsOrd: bannerDataRef.current?.banrLctnNo ?? "", - linkTpCd: "", - patncNm: currentRollingData?.patncNm ?? "", - patnrId: currentRollingData?.patnrId ?? "", + contTpNm: currentRollingData?.shptmBanrTpNm ?? '', + dspyTpNm: bannerDataRef.current?.shptmDspyTpNm ?? '', + expsOrd: bannerDataRef.current?.banrLctnNo ?? '', + linkTpCd: '', + patncNm: currentRollingData?.patncNm ?? '', + patnrId: currentRollingData?.patnrId ?? '', tmplCd: shptmTmplCd, }; } @@ -208,19 +176,17 @@ export default function RollingUnit({ (bannerClick) => { const data = rollingDataRef.current[startIndex]; const newParams = - bannerData.banrLctnNo === "2" + bannerData.banrLctnNo === '2' ? { - bannerType: "Horizontal", + bannerType: 'Horizontal', } : { - bannerType: "Vertical", + bannerType: 'Vertical', }; if (rollingDataRef.current && nowMenu === LOG_MENU.HOME_TOP) { const logParams = { contextName: LOG_CONTEXT_NAME.HOME, - messageId: bannerClick - ? LOG_MESSAGE_ID.BANNER_CLICK - : LOG_MESSAGE_ID.BANNER, + messageId: bannerClick ? LOG_MESSAGE_ID.BANNER_CLICK : LOG_MESSAGE_ID.BANNER, curationId: curationId, curationTitle: curationTitle, contentType: data.shptmBanrTpNm, @@ -228,7 +194,7 @@ export default function RollingUnit({ contentTitle: data.showNm, productId: data.prdtId, productTitle: data.prdtNm, - displayType: "button", + displayType: 'button', partner: data.patncNm, brand: data.brndNm, location: bannerData.banrLctnNo, @@ -251,16 +217,14 @@ export default function RollingUnit({ const deltaTime = time - previousTimeRef.current; if (deltaTime >= 10000 && doRollingRef.current) { - setStartIndex((prevIndex) => - prevIndex === lastIndexRef.current ? 0 : prevIndex + 1 - ); + setStartIndex((prevIndex) => (prevIndex === lastIndexRef.current ? 0 : prevIndex + 1)); previousTimeRef.current = time; } } else { previousTimeRef.current = time; } - if (typeof window === "object") { + if (typeof window === 'object') { requestRef.current = window.requestAnimationFrame(animate); } }, []); @@ -298,7 +262,7 @@ export default function RollingUnit({ // ์ธ๋””์ผ€์ดํ„ฐ ์•„๋ž˜ํ‚ค ๋ˆ„๋ฅผ์‹œ [<] const prevKeyDown = (event) => { - if (event.key === "ArrowDown") { + if (event.key === 'ArrowDown') { setNextFocus(true); setContentsFocus(true); } @@ -306,7 +270,7 @@ export default function RollingUnit({ // ์ธ๋””์ผ€์ดํ„ฐ ์•„๋ž˜ํ‚ค ๋ˆ„๋ฅผ์‹œ [>] const nextKeyDown = (event) => { - if (event.key === "ArrowDown") { + if (event.key === 'ArrowDown') { setPrevFocus(true); setContentsFocus(true); } @@ -364,7 +328,16 @@ export default function RollingUnit({ const handlePushPanel = useCallback( (name, panelInfo) => { - dispatch(pushPanel({ name, panelInfo })); + const isDetailPanel = name === panel_names.DETAIL_PANEL; + const enrichedPanelInfo = isDetailPanel + ? { + sourcePanel: panel_names.HOME_PANEL, + sourceMenu: SOURCE_MENUS.HOME_ROLLING_UNIT, + ...panelInfo, + } + : panelInfo; + + dispatch(pushPanel({ name, panelInfo: enrichedPanelInfo })); }, [dispatch] ); @@ -390,7 +363,7 @@ export default function RollingUnit({ dispatch( sendLogTopContents({ ...topContentsLogInfo, - inDt: formatGMTString(new Date()) ?? "", + inDt: formatGMTString(new Date()) ?? '', logTpNo: LOG_TP_NO.TOP_CONTENTS.CLICK, }) ); @@ -400,26 +373,20 @@ export default function RollingUnit({ switch (linkType) { case LINK_TYPES.FEATURED_BRANDS: handlePushPanel(panel_names.FEATURED_BRANDS_PANEL, { - from: "gnb", + from: 'gnb', patnrId: currentData.patnrId, }); break; case LINK_TYPES.TRENDING_NOW: - handlePushPanel( - panel_names.TRENDING_NOW_PANEL, - createPanelInfo(currentData) - ); + handlePushPanel(panel_names.TRENDING_NOW_PANEL, createPanelInfo(currentData)); break; case LINK_TYPES.HOT_PICKS: if (playerPanelInfo?.modal) { dispatch(finishVideoPreview()); } - handlePushPanel( - panel_names.HOT_PICKS_PANEL, - createPanelInfo(currentData) - ); + handlePushPanel(panel_names.HOT_PICKS_PANEL, createPanelInfo(currentData)); break; case LINK_TYPES.ON_SALE: @@ -430,10 +397,7 @@ export default function RollingUnit({ case LINK_TYPES.CATEGORY: if (Object.keys(categoryData).length > 0) { - handlePushPanel( - panel_names.CATEGORY_PANEL, - createPanelInfo(currentData, categoryData) - ); + handlePushPanel(panel_names.CATEGORY_PANEL, createPanelInfo(currentData, categoryData)); } break; @@ -454,10 +418,7 @@ export default function RollingUnit({ }); break; case LINK_TYPES.JUSTFORYOU: - handlePushPanel( - panel_names.JUST_FOR_YOU_TEST_PANEL, - createPanelInfo(currentData) - ); + handlePushPanel(panel_names.JUST_FOR_YOU_TEST_PANEL, createPanelInfo(currentData)); break; default: return; @@ -466,7 +427,7 @@ export default function RollingUnit({ dispatch( sendLogTopContents({ ...topContentsLogInfo, - inDt: formatGMTString(new Date()) ?? "", + inDt: formatGMTString(new Date()) ?? '', logTpNo: LOG_TP_NO.TOP_CONTENTS.CLICK, }) ); @@ -492,7 +453,7 @@ export default function RollingUnit({ panelInfo: { lastFocusedTargetId, focusedContainerId: TEMPLATE_CODE_CONF.TOP, - currentSpot: currentSpot?.getAttribute("data-spotlight-id"), + currentSpot: currentSpot?.getAttribute('data-spotlight-id'), }, }) ); @@ -508,7 +469,7 @@ export default function RollingUnit({ showUrl: currentData.showUrl, patnrId: currentData.patnrId, showId: currentData.showId, - shptmBanrTpNm: currentData.showId ? currentData.shptmBanrTpNm : "MEDIA", + shptmBanrTpNm: currentData.showId ? currentData.shptmBanrTpNm : 'MEDIA', lgCatCd: currentData.lgCatCd, chanId: currentData.brdcChnlId, modal: false, @@ -520,18 +481,11 @@ export default function RollingUnit({ dispatch( sendLogTopContents({ ...topContentsLogInfo, - inDt: formatGMTString(new Date()) ?? "", + inDt: formatGMTString(new Date()) ?? '', logTpNo: LOG_TP_NO.TOP_CONTENTS.CLICK, }) ); - }, [ - rollingData, - startIndex, - bannerId, - dispatch, - handleStartVideoPlayer, - topContentsLogInfo, - ]); + }, [rollingData, startIndex, bannerId, dispatch, handleStartVideoPlayer, topContentsLogInfo]); // 10์ดˆ ๋กค๋ง useEffect(() => { @@ -540,20 +494,20 @@ export default function RollingUnit({ previousTimeRef.current = undefined; if (rollingDataLength <= 1 || unitHasFocus) { doRollingRef.current = false; - if (typeof window === "object") { + if (typeof window === 'object') { window.cancelAnimationFrame(requestRef.current); } return; } doRollingRef.current = true; - if (typeof window === "object") { + if (typeof window === 'object') { requestRef.current = window.requestAnimationFrame(animate); } return () => { doRollingRef.current = false; - if (typeof window === "object") { + if (typeof window === 'object') { window.cancelAnimationFrame(requestRef.current); } }; @@ -567,7 +521,7 @@ export default function RollingUnit({ const params = { ...topContentsLogInfo, entryMenu: _entryMenu, - inDt: formatGMTString(new Date()) ?? "", + inDt: formatGMTString(new Date()) ?? '', logTpNo: LOG_TP_NO.TOP_CONTENTS.VIEW, nowMenu: _nowMenu, }; @@ -593,10 +547,7 @@ export default function RollingUnit({ return ( @@ -606,15 +557,14 @@ export default function RollingUnit({ onClick={handlePrev} onFocus={indicatorFocus} onBlur={indicatorBlur} - spotlightId={spotlightId + "Prev"} + spotlightId={spotlightId + 'Prev'} spotlightDisabled={prevFocus} onKeyDown={prevKeyDown} aria-label="Move to left Button" /> ) : null} - {filteredRollingData && - filteredRollingData[startIndex].shptmBanrTpNm === "Image Banner" ? ( + {filteredRollingData && filteredRollingData[startIndex].shptmBanrTpNm === 'Image Banner' ? ( - ) : filteredRollingData[startIndex].shptmBanrTpNm === "LIVE" ? ( + ) : filteredRollingData[startIndex].shptmBanrTpNm === 'LIVE' ? (

- +

-
+
{filteredRollingData[startIndex].tmnlImgPath == null ? ( {filteredRollingData[startIndex].tmnlImgPath == null ? ( - "" + '' ) : ( - + )}
@@ -700,7 +634,7 @@ export default function RollingUnit({ />

- ) : filteredRollingData[startIndex].shptmBanrTpNm === "VOD" ? ( + ) : filteredRollingData[startIndex].shptmBanrTpNm === 'VOD' ? ( -
+
{filteredRollingData[startIndex].tmnlImgPath == null ? ( @@ -736,13 +663,9 @@ export default function RollingUnit({
{filteredRollingData[startIndex].tmnlImgPath == null ? ( - "" + '' ) : ( - + )}
@@ -764,8 +687,8 @@ export default function RollingUnit({ className={classNames( css.itemBox, css.todaysDeals, - countryCode === "RU" ? css.ru : "", - countryCode === "DE" ? css.de : "", + countryCode === 'RU' ? css.ru : '', + countryCode === 'DE' ? css.de : '', isHorizontal && css.isHorizontal )} onClick={imageBannerClick} @@ -788,7 +711,7 @@ export default function RollingUnit({ }} />
- {parseFloat(originalPrice?.replace("$", "")) === 0 + {parseFloat(originalPrice?.replace('$', '')) === 0 ? filteredRollingData[startIndex].offerInfo : discountRate ? discountedPrice @@ -797,10 +720,9 @@ export default function RollingUnit({ {originalPrice} )}
- {isHorizontal && - parseFloat(originalPrice?.replace("$", "")) !== 0 && ( - {originalPrice} - )} + {isHorizontal && parseFloat(originalPrice?.replace('$', '')) !== 0 && ( + {originalPrice} + )}
@@ -822,7 +744,7 @@ export default function RollingUnit({ onClick={handleNext} onFocus={indicatorFocus} onBlur={indicatorBlur} - spotlightId={spotlightId + "Next"} + spotlightId={spotlightId + 'Next'} spotlightDisabled={nextFocus} onKeyDown={nextKeyDown} aria-label="Move to right Button" diff --git a/com.twin.app.shoptime/src/views/HomePanel/HomePanel.jsx b/com.twin.app.shoptime/src/views/HomePanel/HomePanel.jsx index cc068871..5ef3e078 100644 --- a/com.twin.app.shoptime/src/views/HomePanel/HomePanel.jsx +++ b/com.twin.app.shoptime/src/views/HomePanel/HomePanel.jsx @@ -244,6 +244,31 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => { const focusedContainerIdRef = useRef(null); const prevIsOnTopRef = useRef(isOnTop); + // โœ… [251124] HomePanel์„ ๋ฒ—์–ด๋‚  ๋•Œ(isOnTop: true -> false) ํ˜„์žฌ ํฌ์ปค์Šค ์ €์žฅ + useEffect(() => { + if (prevIsOnTopRef.current && !isOnTop) { + const current = Spotlight.getCurrent(); + const tBody = document.querySelector(`[data-spotlight-id="${SpotlightIds.HOME_TBODY}"]`); + + if (current && tBody && tBody.contains(current)) { + const targetId = current.getAttribute('data-spotlight-id'); + if (targetId) { + dlog('[HomePanel] Saving focus before leaving:', targetId); + lastFocusedTargetRef.current = targetId; + dispatch( + updateHomeInfo({ + name: panel_names.HOME_PANEL, + panelInfo: { + lastFocusedTargetId: targetId, + }, + }) + ); + } + } + } + prevIsOnTopRef.current = isOnTop; + }, [isOnTop, dispatch]); + // โœ… [251119] DetailPanelBackground ์ด๋ฏธ์ง€ ํ”„๋ฆฌ๋กœ๋”ฉ // HomePanel ๋งˆ์šดํŠธ ์‹œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ๋กœ ๋ชจ๋“  ํŒŒํŠธ๋„ˆ์‚ฌ ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€๋ฅผ ๋ฏธ๋ฆฌ ๋กœ๋“œํ•˜์—ฌ // DetailPanel ์ง„์ž… ์‹œ ๋กœ๋”ฉ ์ง€์—ฐ์„ ๋ฐฉ์ง€ํ•จ diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx index f09d05b0..2818d20f 100644 --- a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx +++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx @@ -39,6 +39,7 @@ import { pauseModalVideo, resumeModalVideo, resumeFullscreenVideo, + updateVideoPlayState, } from '../../actions/playActions'; import { resetPlayerOverlays } from '../../actions/videoPlayActions'; import { convertUtcToLocal } from '../../components/MediaPlayer/util'; @@ -436,17 +437,153 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props } }, [isOnTop, panelInfo]); - // ์ƒˆ๋กœ์šด useEffect ์ถ”๊ฐ€ (๋ผ์ธ 328 ์ดํ›„) + // ์ด์ „ ์ƒํƒœ ์ €์žฅ์„ ์œ„ํ•œ ref + const previousVideoPlayState = useRef(null); + const previousPanelInfo = useRef(null); + const previousVideoSource = useRef(null); + const previousIsOnTop = useRef(null); + + // Redux ์ƒํƒœ ๋ณ€ํ™” ๋ชจ๋‹ˆํ„ฐ๋ง useEffect (์ค‘์š” ๋ณ€ํ™”๋งŒ) 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(); + // 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)) + ) { + console.log('๐Ÿ“Š [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) { + // ์ƒํƒœ ๋ณ€๊ฒฝ ์‹œ์—๋งŒ ๋””๋ฒ„๊น… ๋กœ๊ทธ ์ถœ๋ ฅ + console.log('๐Ÿ” [PlayerPanel] PanelInfo isPaused changed', { + previousIsPaused: previousPanelInfo.current?.isPaused, + currentIsPaused: panelInfo.isPaused, + isOnTop, + videoPlayerExists: !!videoPlayer.current, + currentPlayingUrl, + timestamp: new Date().toISOString(), + }); + console.log('๐ŸŽฎ [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) { + console.log('๐Ÿ”ด [PlayerPanel] Calling VideoPlayer.pause() due to PanelInfo change'); + videoPlayer.current.pause(); + } else if (panelInfo.isPaused === false) { + console.log('๐ŸŸข [PlayerPanel] Calling VideoPlayer.play() due to PanelInfo change'); + videoPlayer.current.play(); + } } } - }, [panelInfo?.isPaused]); + + 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'; + + console.log('๐ŸŽฌ [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) { + console.log('๐Ÿ“Š [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(() => { @@ -1130,11 +1267,63 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props // ์ตœ์ƒ๋‹จ ํŒจ๋„์ด DetailPanel์ด๊ณ  PlayerPanel์—์„œ ์ง„์ž…ํ–ˆ๋Š”์ง€ ํ™•์ธ const isTopPanelDetailFromPlayer = useMemo(() => { - return ( + const result = topPanel?.name === panel_names.DETAIL_PANEL && - topPanel?.panelInfo?.launchedFromPlayer === true - ); - }, [topPanel]); + topPanel?.panelInfo?.launchedFromPlayer === true; + + // ๐Ÿ” DetailPanel ์ƒํƒœ ๋ณ€ํ™” ๋กœ๊น… + if (result) { + console.log('๐ŸŽฌ [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) { + console.log('๐Ÿ”ด [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) { + console.log('๐Ÿ”„ [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; @@ -2624,6 +2813,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props tabContainerVersion={tabContainerVersion} tabIndexV2={tabIndexV2} dispatch={dispatch} + videoPlayState={videoPlayState} > {typeof window === 'object' && window.PalmSystem && (