[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; panelWorkingAction = updatePanel;
} }
// playerState 업데이트: 기존 playerState와 새 데이터 병합 // 중복 실행 방지: 현재 PlayerPanel이 같은 배너를 재생 중이면 skip
const currentPlayerState = topPanel?.panelInfo?.playerState || {}; const currentPlayerState = topPanel?.panelInfo?.playerState || {};
if (currentPlayerState?.currentBannerId === bannerId) {
// ✅ 정확한 비교: 같은 배너 + 같은 modal 상태 + 같은 위치일 때만 skip console.log('[playActions] startVideoPlayerNew: 동일 배너 재생 중이라 중복 실행 SKIP', {
if (shouldSkipVideoPlayback(topPanel?.panelInfo, modal, modalContainerId, bannerId)) { bannerId,
});
return; return;
} }

View File

@@ -51,7 +51,13 @@ const SpottableComponent = Spottable('div');
const Container = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div'); const Container = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
const ContainerBasic = 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(); const dispatch = useDispatch();
useEffect(() => { useEffect(() => {
dispatch(justForYou()); dispatch(justForYou());
@@ -453,6 +459,7 @@ export default function HomeBanner({ firstSpot, spotlightId, handleItemFocus, ha
handleItemFocus={_handleItemFocus} handleItemFocus={_handleItemFocus}
randomNumber={data.randomIndex} randomNumber={data.randomIndex}
videoPlayerable={videoPlayerable} videoPlayerable={videoPlayerable}
videoPlayIntentRef={videoPlayIntentRef}
/> />
) : ( ) : (
<SpottableComponent spotlightId={'banner' + index}> <SpottableComponent spotlightId={'banner' + index}>
@@ -472,7 +479,7 @@ export default function HomeBanner({ firstSpot, spotlightId, handleItemFocus, ha
</div> </div>
); );
}, },
[_handleItemFocus, _handleShelfFocus, bannerDataList] [_handleItemFocus, _handleShelfFocus, bannerDataList, videoPlayIntentRef]
); );
// 0번째 배너(영구 재생)를 위한 렌더링 함수 // 0번째 배너(영구 재생)를 위한 렌더링 함수

View File

@@ -50,6 +50,7 @@ export default function RandomUnit({
handleItemFocus, handleItemFocus,
randomNumber, randomNumber,
videoPlayerable = false, videoPlayerable = false,
videoPlayIntentRef,
}) { }) {
const dispatch = useDispatch(); const dispatch = useDispatch();
@@ -83,13 +84,16 @@ export default function RandomUnit({
const [isFocused, setIsFocused] = useState(false); const [isFocused, setIsFocused] = useState(false);
const [videoError, setVideoError] = useState(false); const [videoError, setVideoError] = useState(false);
const [liveIndicies, setLiveIndicies] = useState([]); const [liveIndicies, setLiveIndicies] = useState([]);
const [videoIntentVersion, setVideoIntentVersion] = useState(0);
const defaultFocus = useSelector((state) => state.home.defaultFocus); const defaultFocus = useSelector((state) => state.home.defaultFocus);
const isVideoTransitionLocked = useSelector((state) => state.home.videoTransitionLocked); const isVideoTransitionLocked = useSelector((state) => state.home.videoTransitionLocked);
const timerRef = useRef(); const timerRef = useRef();
const hasAutoPlayStartedRef = useRef(false); const hasAutoPlayStartedRef = useRef(false);
const hasPlaybackStartedRef = useRef(false); const hasPlaybackStartedRef = useRef(false);
const videoIntentTimerRef = useRef(null);
const isDefaultAutoPlayTarget = defaultFocus === spotlightId; const isDefaultAutoPlayTarget = defaultFocus === spotlightId;
const lastProcessedIntentVersionRef = useRef(-1);
const bannerDataRef = useRef(bannerData); const bannerDataRef = useRef(bannerData);
const randomDataRef = useRef( const randomDataRef = useRef(
bannerDetailInfos && randomNumber !== undefined && bannerDetailInfos.length > 0 bannerDetailInfos && randomNumber !== undefined && bannerDetailInfos.length > 0
@@ -273,19 +277,45 @@ export default function RandomUnit({
lastUpdate: videoPlayState.lastUpdate, lastUpdate: videoPlayState.lastUpdate,
}); });
console.log('[RandomUnit] onFocus called', { console.log('[RandomUnit] onFocus - 비디오 재생 의도 저장', {
spotlightId, bannerId: spotlightId,
videoPlayerable, videoPlayerable,
currentIntent: videoPlayIntentRef.current?.bannerId,
currentVideoBannerId, currentVideoBannerId,
randomData: randomData?.showId || 'no-show-id', randomData: randomData?.showId || 'no-show-id',
}); });
setIsFocused(true); setIsFocused(true);
// video가 플레이 가능한 경우: 다른 배너에서 비디오 재생 중이면 종료 // 🔽 [251116] 수정: 재생 의도만 저장, 즉시 실행 ❌
if (videoPlayerable && currentVideoBannerId && currentVideoBannerId !== spotlightId) { if (videoPlayerable) {
console.log('[RandomUnit] videoPlayerable=true and different banner, finishing video'); videoPlayIntentRef.current = {
dispatch(finishVideoPreview()); 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로 축소 // video가 플레이 불가능한 경우: 재생 중인 비디오를 1px로 축소
@@ -618,6 +648,15 @@ export default function RandomUnit({
} }
}, [randomData]); }, [randomData]);
// 🔽 [251116] 기존 로직 단순화 - videoPlayIntentRef가 모든 비디오 제어를 담당
useEffect(() => {
if (!isFocused) {
setVideoError(false);
}
}, [isFocused]);
// 🔽 [251116] 기존 복잡한 비디오 시작 로직 비활성화
/*
useEffect(() => { useEffect(() => {
if (isFocused && !videoError && !hasPlaybackStartedRef.current) { if (isFocused && !videoError && !hasPlaybackStartedRef.current) {
if (timerRef.current) { if (timerRef.current) {
@@ -665,6 +704,89 @@ export default function RandomUnit({
spotlightId, spotlightId,
handleStartVideo, 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(() => { useEffect(() => {
if (isFocused && broadcast?.type === 'videoError') { if (isFocused && broadcast?.type === 'videoError') {

View File

@@ -89,6 +89,8 @@ const HomePanel = ({ isOnTop }) => {
enableLogging: true, enableLogging: true,
logPrefix: '[HomePanel-VideoPlay]', logPrefix: '[HomePanel-VideoPlay]',
}); });
// 🔽 비디오 재생 의도 공유 ref
const videoPlayIntentRef = useRef(null);
// 🔽 useVideoMove - 포커스 전환 기반 동영상 제어 // 🔽 useVideoMove - 포커스 전환 기반 동영상 제어
// [COMMENTED OUT] useVideoMove 미사용 - cleanup() 호출되지 않음 // [COMMENTED OUT] useVideoMove 미사용 - cleanup() 호출되지 않음
@@ -314,6 +316,7 @@ const HomePanel = ({ isOnTop }) => {
el.shptmApphmDspyOptNm el.shptmApphmDspyOptNm
)} )}
handleItemFocus={handleItemFocus(el.shptmApphmDspyOptCd)} handleItemFocus={handleItemFocus(el.shptmApphmDspyOptCd)}
videoPlayIntentRef={videoPlayIntentRef}
/> />
); );
} }