[251116] feat: videoPlayIntentRef

🕐 커밋 시간: 2025. 11. 16. 19:59:28

📊 변경 통계:
  • 총 파일: 4개
  • 추가: +149줄
  • 삭제: -16줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/actions/playActions.js
  ~ com.twin.app.shoptime/src/views/HomePanel/HomeBanner/HomeBanner.jsx
  ~ com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx
  ~ com.twin.app.shoptime/src/views/HomePanel/HomePanel.jsx

🔧 함수 변경 내용:
  📄 com.twin.app.shoptime/src/views/HomePanel/HomeBanner/HomeBanner.jsx (javascript):
    🔄 Modified: Spottable()
  📄 com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx (javascript):
     Added: SpotlightContainerDecorator()

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
This commit is contained in:
2025-11-16 19:59:28 +09:00
parent 341af91564
commit da1a050a10
4 changed files with 145 additions and 12 deletions

View File

@@ -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;
}

View File

@@ -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}
/>
) : (
<SpottableComponent spotlightId={'banner' + index}>
@@ -472,7 +479,7 @@ export default function HomeBanner({ firstSpot, spotlightId, handleItemFocus, ha
</div>
);
},
[_handleItemFocus, _handleShelfFocus, bannerDataList]
[_handleItemFocus, _handleShelfFocus, bannerDataList, videoPlayIntentRef]
);
// 0번째 배너(영구 재생)를 위한 렌더링 함수

View File

@@ -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') {

View File

@@ -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}
/>
);
}