diff --git a/com.twin.app.shoptime/src/actions/playActions.js b/com.twin.app.shoptime/src/actions/playActions.js index a6346ceb..fecf1154 100644 --- a/com.twin.app.shoptime/src/actions/playActions.js +++ b/com.twin.app.shoptime/src/actions/playActions.js @@ -161,11 +161,12 @@ export const startVideoPlayerNew = panelWorkingAction = updatePanel; } - // playerState 업데이트: 기존 playerState와 새 데이터 병합 + // 중복 실행 방지: 현재 PlayerPanel이 같은 배너를 재생 중이면 skip const currentPlayerState = topPanel?.panelInfo?.playerState || {}; - - // ✅ 정확한 비교: 같은 배너 + 같은 modal 상태 + 같은 위치일 때만 skip - if (shouldSkipVideoPlayback(topPanel?.panelInfo, modal, modalContainerId, bannerId)) { + if (currentPlayerState?.currentBannerId === bannerId) { + console.log('[playActions] startVideoPlayerNew: 동일 배너 재생 중이라 중복 실행 SKIP', { + bannerId, + }); return; } diff --git a/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/HomeBanner.jsx b/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/HomeBanner.jsx index 4dd01426..1c2eb567 100644 --- a/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/HomeBanner.jsx +++ b/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/HomeBanner.jsx @@ -51,7 +51,13 @@ const SpottableComponent = Spottable('div'); const Container = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div'); const ContainerBasic = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div'); -export default function HomeBanner({ firstSpot, spotlightId, handleItemFocus, handleShelfFocus }) { +export default function HomeBanner({ + firstSpot, + spotlightId, + handleItemFocus, + handleShelfFocus, + videoPlayIntentRef, +}) { const dispatch = useDispatch(); useEffect(() => { dispatch(justForYou()); @@ -453,6 +459,7 @@ export default function HomeBanner({ firstSpot, spotlightId, handleItemFocus, ha handleItemFocus={_handleItemFocus} randomNumber={data.randomIndex} videoPlayerable={videoPlayerable} + videoPlayIntentRef={videoPlayIntentRef} /> ) : ( @@ -472,7 +479,7 @@ export default function HomeBanner({ firstSpot, spotlightId, handleItemFocus, ha ); }, - [_handleItemFocus, _handleShelfFocus, bannerDataList] + [_handleItemFocus, _handleShelfFocus, bannerDataList, videoPlayIntentRef] ); // 0번째 배너(영구 재생)를 위한 렌더링 함수 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 276ff27a..5aa5cba4 100644 --- a/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx +++ b/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx @@ -50,6 +50,7 @@ export default function RandomUnit({ handleItemFocus, randomNumber, videoPlayerable = false, + videoPlayIntentRef, }) { const dispatch = useDispatch(); @@ -83,13 +84,16 @@ export default function RandomUnit({ const [isFocused, setIsFocused] = useState(false); const [videoError, setVideoError] = useState(false); const [liveIndicies, setLiveIndicies] = useState([]); + const [videoIntentVersion, setVideoIntentVersion] = useState(0); const defaultFocus = useSelector((state) => state.home.defaultFocus); const isVideoTransitionLocked = useSelector((state) => state.home.videoTransitionLocked); const timerRef = useRef(); const hasAutoPlayStartedRef = useRef(false); const hasPlaybackStartedRef = useRef(false); + const videoIntentTimerRef = useRef(null); const isDefaultAutoPlayTarget = defaultFocus === spotlightId; + const lastProcessedIntentVersionRef = useRef(-1); const bannerDataRef = useRef(bannerData); const randomDataRef = useRef( bannerDetailInfos && randomNumber !== undefined && bannerDetailInfos.length > 0 @@ -273,19 +277,45 @@ export default function RandomUnit({ lastUpdate: videoPlayState.lastUpdate, }); - console.log('[RandomUnit] onFocus called', { - spotlightId, + console.log('[RandomUnit] onFocus - 비디오 재생 의도 저장', { + bannerId: spotlightId, videoPlayerable, + currentIntent: videoPlayIntentRef.current?.bannerId, currentVideoBannerId, randomData: randomData?.showId || 'no-show-id', }); setIsFocused(true); - // video가 플레이 가능한 경우: 다른 배너에서 비디오 재생 중이면 종료 - if (videoPlayerable && currentVideoBannerId && currentVideoBannerId !== spotlightId) { - console.log('[RandomUnit] videoPlayerable=true and different banner, finishing video'); - dispatch(finishVideoPreview()); + // 🔽 [251116] 수정: 재생 의도만 저장, 즉시 실행 ❌ + if (videoPlayerable) { + videoPlayIntentRef.current = { + bannerId: spotlightId, + videoProps: { + bannerId: spotlightId, + showUrl: randomData.showUrl, + patnrId: randomData.patnrId, + showId: randomData.showId, + shptmBanrTpNm: randomData.shptmBanrTpNm, + lgCatCd: randomData.lgCatCd, + chanId: randomData.brdcChnlId, + modal: true, + modalContainerId: spotlightId, + }, + timestamp: Date.now(), + }; + + console.log('[RandomUnit] 비디오 재생 의도 저장됨', { + intentBannerId: spotlightId, + videoUrl: randomData.showUrl, + }); + + setVideoIntentVersion((version) => version + 1); + + // 기존 finishVideoPreview 호출 제거! + // if (videoPlayerable && currentVideoBannerId && currentVideoBannerId !== spotlightId) { + // dispatch(finishVideoPreview()); + // } } // video가 플레이 불가능한 경우: 재생 중인 비디오를 1px로 축소 @@ -618,6 +648,15 @@ export default function RandomUnit({ } }, [randomData]); + // 🔽 [251116] 기존 로직 단순화 - videoPlayIntentRef가 모든 비디오 제어를 담당 + useEffect(() => { + if (!isFocused) { + setVideoError(false); + } + }, [isFocused]); + + // 🔽 [251116] 기존 복잡한 비디오 시작 로직 비활성화 + /* useEffect(() => { if (isFocused && !videoError && !hasPlaybackStartedRef.current) { if (timerRef.current) { @@ -665,6 +704,89 @@ export default function RandomUnit({ spotlightId, handleStartVideo, ]); + */ + + // 🔽 [251116] 신규: videoPlayIntentRef 기반 비디오 제어 + useEffect(() => { + if (!videoPlayIntentRef.current) { + return undefined; + } + if (lastProcessedIntentVersionRef.current === videoIntentVersion) { + return undefined; + } + lastProcessedIntentVersionRef.current = videoIntentVersion; + + const intent = videoPlayIntentRef.current; + const currentVideoId = videoPlayState.videoId; + const activeBannerId = currentVideoBannerId; + + console.log('[RandomUnit] videoPlayIntent useEffect', { + intentBannerId: intent.bannerId, + currentVideoId, + activeBannerId, + currentPlayback: videoPlayState.playback, + currentDisplay: videoPlayState.display, + loadingProgress: videoPlayState.loadingProgress, + shouldStart: intent.bannerId !== activeBannerId, + isVideoTransitionLocked, + timestamp: Date.now(), + }); + + if (intent.bannerId === activeBannerId) { + return undefined; + } + + if (videoIntentTimerRef.current) { + clearTimeout(videoIntentTimerRef.current); + } + + videoIntentTimerRef.current = setTimeout(() => { + if (videoPlayIntentRef.current?.bannerId !== intent.bannerId) { + return; + } + + console.log('[RandomUnit] 비디오 변경 실행', { + from: currentVideoId, + to: intent.bannerId, + }); + + if (activeBannerId) { + dispatch(finishVideoPreview()); + + setTimeout(() => { + dispatch(setVideoTransitionLock(true)); + dispatch(startVideoPlayerNew(intent.videoProps)); + hasPlaybackStartedRef.current = true; + + setTimeout(() => { + dispatch(setVideoTransitionLock(false)); + }, 2000); + }, 100); + } else { + dispatch(setVideoTransitionLock(true)); + dispatch(startVideoPlayerNew(intent.videoProps)); + hasPlaybackStartedRef.current = true; + + setTimeout(() => { + dispatch(setVideoTransitionLock(false)); + }, 2000); + } + }, 300); // 안정화 시간 + + return () => { + if (videoIntentTimerRef.current) { + clearTimeout(videoIntentTimerRef.current); + videoIntentTimerRef.current = null; + } + }; + }, [ + videoPlayState.videoId, + dispatch, + videoPlayIntentRef, + videoIntentVersion, + isVideoTransitionLocked, + currentVideoBannerId, + ]); useEffect(() => { if (isFocused && broadcast?.type === 'videoError') { diff --git a/com.twin.app.shoptime/src/views/HomePanel/HomePanel.jsx b/com.twin.app.shoptime/src/views/HomePanel/HomePanel.jsx index 24f75f6f..f03becbc 100644 --- a/com.twin.app.shoptime/src/views/HomePanel/HomePanel.jsx +++ b/com.twin.app.shoptime/src/views/HomePanel/HomePanel.jsx @@ -89,6 +89,8 @@ const HomePanel = ({ isOnTop }) => { enableLogging: true, logPrefix: '[HomePanel-VideoPlay]', }); + // 🔽 비디오 재생 의도 공유 ref + const videoPlayIntentRef = useRef(null); // 🔽 useVideoMove - 포커스 전환 기반 동영상 제어 // [COMMENTED OUT] useVideoMove 미사용 - cleanup() 호출되지 않음 @@ -314,6 +316,7 @@ const HomePanel = ({ isOnTop }) => { el.shptmApphmDspyOptNm )} handleItemFocus={handleItemFocus(el.shptmApphmDspyOptCd)} + videoPlayIntentRef={videoPlayIntentRef} /> ); }