diff --git a/com.twin.app.shoptime/[251116]_video_state_management_design.md b/com.twin.app.shoptime/[251116]_video_state_management_design.md new file mode 100644 index 00000000..c96fd3f2 --- /dev/null +++ b/com.twin.app.shoptime/[251116]_video_state_management_design.md @@ -0,0 +1,220 @@ +# [251116] 새로운 비디오 상태 관리 시스템 구현 + +## 개요 + +기존의 videoPlayReducer는 유지하되, PlayerPanel과 VideoPlayer.js를 위한 새로운 비디오 상태 관리 시스템을 playerReducer에 구현한다. 재생 상태와 화면 상태를 분리하여 더 정밀한 비디오 상태 제어를 가능하게 한다. + +## 설계 원칙 + +1. **기존 videoPlayReducer 유지**: 다른 컴포넌트에서 사용 중일 수 있으므로 그대로 둔다 +2. **playerReducer에 새로운 상태 시스템 구현**: PlayerPanel과 VideoPlayer.js 전용 +3. **이중 상태 관리**: 재생 상태(Playback Status) + 화면 상태(Display Status) +4. **기존 패턴 따르기**: FP 스타일의 curry, get, set 활용 + +## 새로운 상태 구조 + +### 상수 정의 (playerActions.js) + +```javascript +// 재생 상태 +export const PLAYBACK_STATUS = { + LOADING: 'loading', // 비디오 로딩 중 + LOAD_SUCCESS: 'load_success', // 비디오 로딩 성공 + LOAD_ERROR: 'load_error', // 비디오 로딩 오류 + PLAYING: 'playing', // 비디오 재생 중 + NOT_PLAYING: 'not_playing', // 비디오 재생 아님 (정지/일시정지) + BUFFERING: 'buffering' // 버퍼링 중 +}; + +// 화면 상태 +export const DISPLAY_STATUS = { + HIDDEN: 'hidden', // 화면에 안보임 + VISIBLE: 'visible', // 화면에 보임 + MINIMIZED: 'minimized', // 최소화됨 + FULLSCREEN: 'fullscreen' // 전체화면 +}; +``` + +### 초기 상태 (playerReducer) + +```javascript +// 기존 playerReducer 상태에 추가 +const initialState = { + // ... 기존 상태들 + + playerVideoState: { + // 현재 상태 + playback: PLAYBACK_STATUS.NOT_PLAYING, + display: DISPLAY_STATUS.HIDDEN, + videoId: null, + progress: 0, + error: null, + timestamp: null + } +}; +``` + +## 액션 타입 및 함수 + +### 액션 타입 + +```javascript +export const PLAYER_VIDEO_ACTIONS = { + // 재생 상태 액션 + SET_PLAYBACK_LOADING: 'SET_PLAYBACK_LOADING', + SET_PLAYBACK_SUCCESS: 'SET_PLAYBACK_SUCCESS', + SET_PLAYBACK_ERROR: 'SET_PLAYBACK_ERROR', + SET_PLAYBACK_PLAYING: 'SET_PLAYBACK_PLAYING', + SET_PLAYBACK_NOT_PLAYING: 'SET_PLAYBACK_NOT_PLAYING', + SET_PLAYBACK_BUFFERING: 'SET_PLAYBACK_BUFFERING', + + // 화면 상태 액션 + SET_DISPLAY_HIDDEN: 'SET_DISPLAY_HIDDEN', + SET_DISPLAY_VISIBLE: 'SET_DISPLAY_VISIBLE', + SET_DISPLAY_MINIMIZED: 'SET_DISPLAY_MINIMIZED', + SET_DISPLAY_FULLSCREEN: 'SET_DISPLAY_FULLSCREEN', + + // 복합 액션 + SET_VIDEO_LOADING: 'SET_VIDEO_LOADING', + SET_VIDEO_PLAYING: 'SET_VIDEO_PLAYING', + SET_VIDEO_STOPPED: 'SET_VIDEO_STOPPED', + SET_VIDEO_MINIMIZED_PLAYING: 'SET_VIDEO_MINIMIZED_PLAYING', +}; +``` + +### 액션 함수 + +```javascript +// 기본 액션 함수들 (FP 스타일) +export const setPlaybackLoading = curry((videoId, displayMode = 'visible') => ({ + type: PLAYER_VIDEO_ACTIONS.SET_VIDEO_LOADING, + payload: { + playback: PLAYBACK_STATUS.LOADING, + display: displayMode, + videoId, + progress: 0, + error: null, + timestamp: Date.now() + } +})); + +export const setPlaybackPlaying = curry((videoId, displayMode = 'fullscreen') => ({ + type: PLAYER_VIDEO_ACTIONS.SET_VIDEO_PLAYING, + payload: { + playback: PLAYBACK_STATUS.PLAYING, + display: displayMode, + videoId, + progress: 100, + error: null, + timestamp: Date.now() + } +})); + +export const setPlaybackError = curry((videoId, error) => ({ + type: PLAYER_VIDEO_ACTIONS.SET_PLAYBACK_ERROR, + payload: { + playback: PLAYBACK_STATUS.LOAD_ERROR, + display: DISPLAY_STATUS.VISIBLE, + videoId, + error, + progress: 0, + timestamp: Date.now() + } +})); + +export const setVideoStopped = () => ({ + type: PLAYER_VIDEO_ACTIONS.SET_VIDEO_STOPPED, + payload: { + playback: PLAYBACK_STATUS.NOT_PLAYING, + display: DISPLAY_STATUS.HIDDEN, + videoId: null, + error: null, + progress: 0, + timestamp: Date.now() + } +})); +``` + +## 상태 사용 예시 + +### PlayerPanel.jsx + +```javascript +import { + setPlaybackLoading, + setPlaybackPlaying, + setPlaybackError, + setVideoStopped +} from '../actions/playerActions'; + +// 비디오 로딩 시작 +const handleVideoLoadStart = (videoId) => { + dispatch(setPlaybackLoading(videoId, 'fullscreen')); +}; + +// 비디오 재생 시작 +const handleVideoPlay = (videoId) => { + dispatch(setPlaybackPlaying(videoId, 'fullscreen')); +}; + +// 비디오 에러 발생 +const handleVideoError = (videoId, error) => { + dispatch(setPlaybackError(videoId, error)); +}; + +// 상태 확인 +const videoState = useSelector(state => state.player.playerVideoState); +const isLoading = videoState.playback === PLAYBACK_STATUS.LOADING; +const isPlaying = videoState.playback === PLAYBACK_STATUS.PLAYING; +const hasError = videoState.playback === PLAYBACK_STATUS.LOAD_ERROR; +const isFullscreen = videoState.display === DISPLAY_STATUS.FULLSCREEN; +``` + +### VideoPlayer.js + +```javascript +// 현재 상태에 따른 UI 렌더링 +const renderVideoState = () => { + const { playback, display, error, progress } = videoState; + + if (playback === PLAYBACK_STATUS.LOADING) { + return ; + } + + if (playback === PLAYBACK_STATUS.LOAD_ERROR) { + return ; + } + + if (playback === PLAYBACK_STATUS.BUFFERING) { + return ; + } + + if (playback === PLAYBACK_STATUS.PLAYING && display === DISPLAY_STATUS.FULLSCREEN) { + return ; + } + + return null; +}; +``` + +## 구현 순서 + +1. [ ] playerActions.js에 상수 및 액션 함수들 추가 +2. [ ] playerReducer.js에 초기 상태 및 핸들러들 추가 +3. [ ] PlayerPanel.jsx에서 새로운 상태 시스템으로 전환 +4. [ ] VideoPlayer.js에서 새로운 상태 시스템으로 전환 +5. [ ] 테스트 및 검증 + +## 장점 + +1. **정밀한 상태 제어**: 재생 상태와 화면 상태를 별도로 관리 +2. **명확한 상태 의미**: 각 상태가 명확한 의미를 가짐 +3. **확장성**: 새로운 상태 추가가 용이 +4. **유지보수성**: 기존 코드 영향 최소화 +5. **재사용성**: 다른 컴포넌트에서도 활용 가능 + +## 주의사항 + +- 기존 videoPlayReducer와 충돌하지 않도록 주의 +- PlayerPanel과 VideoPlayer.js에만 집중하여 구현 +- 기존 비디오 재생 로직과 호환성 유지 \ No newline at end of file diff --git a/com.twin.app.shoptime/src/actions/actionTypes.js b/com.twin.app.shoptime/src/actions/actionTypes.js index b2bc63ea..0507bedb 100644 --- a/com.twin.app.shoptime/src/actions/actionTypes.js +++ b/com.twin.app.shoptime/src/actions/actionTypes.js @@ -259,6 +259,27 @@ export const types = { CLEAR_PLAYER_INFO: 'CLEAR_PLAYER_INFO', UPDATE_VIDEO_PLAY_STATE: 'UPDATE_VIDEO_PLAY_STATE', + // 🔽 [251116] 새로운 비디오 상태 관리 시스템 - 재생 상태 + SET_PLAYBACK_LOADING: 'SET_PLAYBACK_LOADING', + SET_PLAYBACK_SUCCESS: 'SET_PLAYBACK_SUCCESS', + SET_PLAYBACK_ERROR: 'SET_PLAYBACK_ERROR', + SET_PLAYBACK_PLAYING: 'SET_PLAYBACK_PLAYING', + SET_PLAYBACK_NOT_PLAYING: 'SET_PLAYBACK_NOT_PLAYING', + SET_PLAYBACK_BUFFERING: 'SET_PLAYBACK_BUFFERING', + + // 🔽 [251116] 새로운 비디오 상태 관리 시스템 - 화면 상태 + SET_DISPLAY_HIDDEN: 'SET_DISPLAY_HIDDEN', + SET_DISPLAY_VISIBLE: 'SET_DISPLAY_VISIBLE', + SET_DISPLAY_MINIMIZED: 'SET_DISPLAY_MINIMIZED', + SET_DISPLAY_FULLSCREEN: 'SET_DISPLAY_FULLSCREEN', + + // 🔽 [251116] 복합 상태 액션들 + SET_VIDEO_LOADING: 'SET_VIDEO_LOADING', + SET_VIDEO_PLAYING: 'SET_VIDEO_PLAYING', + SET_VIDEO_STOPPED: 'SET_VIDEO_STOPPED', + SET_VIDEO_MINIMIZED_PLAYING: 'SET_VIDEO_MINIMIZED_PLAYING', + SET_VIDEO_ERROR: 'SET_VIDEO_ERROR', + // 🔽 [추가] 플레이 제어 매니저 액션 타입 /** * 홈 화면 배너의 비디오 재생 제어를 위한 액션 타입. diff --git a/com.twin.app.shoptime/src/actions/playActions.js b/com.twin.app.shoptime/src/actions/playActions.js index 463074a6..2abd75c8 100644 --- a/com.twin.app.shoptime/src/actions/playActions.js +++ b/com.twin.app.shoptime/src/actions/playActions.js @@ -6,6 +6,24 @@ import { panel_names } from '../utils/Config'; import { types } from './actionTypes'; import { popPanel, pushPanel, updatePanel } from './panelActions'; +// 🔽 [251116] 새로운 비디오 상태 관리 시스템 - 재생 상태 +export const PLAYBACK_STATUS = { + LOADING: 'loading', + LOAD_SUCCESS: 'load_success', + LOAD_ERROR: 'load_error', + PLAYING: 'playing', + NOT_PLAYING: 'not_playing', + BUFFERING: 'buffering', +}; + +// 🔽 [251116] 새로운 비디오 상태 관리 시스템 - 화면 상태 +export const DISPLAY_STATUS = { + HIDDEN: 'hidden', + VISIBLE: 'visible', + MINIMIZED: 'minimized', + FULLSCREEN: 'fullscreen', +}; + //yhcho /* dispatch(startVideoPreview({ @@ -95,7 +113,15 @@ const shouldSkipVideoPlayback = ( }; export const startVideoPlayerNew = - ({ modal, modalContainerId, modalClassName, spotlightDisable, useNewPlayer, bannerId, ...rest }) => + ({ + modal, + modalContainerId, + modalClassName, + spotlightDisable, + useNewPlayer, + bannerId, + ...rest + }) => (dispatch, getState) => { const panels = getState().panels.panels; const topPanel = panels[panels.length - 1]; @@ -146,7 +172,7 @@ export const startVideoPlayerNew = export const finishVideoPreview = () => (dispatch, getState) => { const panels = getState().panels.panels; - const topPanel = panels[panels.length-1]; + const topPanel = panels[panels.length - 1]; if (topPanel && topPanel.name === panel_names.PLAYER_PANEL && topPanel.panelInfo.modal) { if (startVideoFocusTimer) { clearTimeout(startVideoFocusTimer); @@ -179,9 +205,7 @@ export const finishAllVideoForce = () => (dispatch, getState) => { const panels = getState().panels.panels; // 모든 PlayerPanel이 존재하는지 확인 (스택 어디에 있든) - const hasPlayerPanel = panels.some( - (panel) => panel.name === panel_names.PLAYER_PANEL - ); + const hasPlayerPanel = panels.some((panel) => panel.name === panel_names.PLAYER_PANEL); if (hasPlayerPanel) { if (startVideoFocusTimer) { @@ -352,7 +376,10 @@ export const expandVideoFrom1px = () => (dispatch, getState) => { // 축소된 modal PlayerPanel 찾기 const shrunkModalPlayerPanel = panels.find( - (panel) => panel.name === panel_names.PLAYER_PANEL && panel.panelInfo?.modal && panel.panelInfo?.shouldShrinkTo1px + (panel) => + panel.name === panel_names.PLAYER_PANEL && + panel.panelInfo?.modal && + panel.panelInfo?.shouldShrinkTo1px ); if (shrunkModalPlayerPanel) { @@ -581,7 +608,6 @@ export const goToFullScreen = () => (dispatch, getState) => { ); }; - /** * 영구재생 비디오를 일시정지 상태로 만듭니다. (내부 사용) */ @@ -617,3 +643,152 @@ export const returnToPreview = () => (dispatch, getState) => { dispatch(finishVideoPreview()); } }; + +/* 🔽 [251116] 새로운 비디오 상태 관리 시스템 - 액션 함수들 */ + +/** + * 비디오 로딩 시작 상태로 설정 + * @param {string} videoId - 비디오 ID + * @param {string} displayMode - 화면 모드 ('visible', 'fullscreen', 'minimized') + */ +export const setPlaybackLoading = (videoId, displayMode = DISPLAY_STATUS.VISIBLE) => ({ + type: types.SET_VIDEO_LOADING, + payload: { + playback: PLAYBACK_STATUS.LOADING, + display: displayMode, + videoId, + loadingProgress: 0, + loadingError: null, + lastUpdate: Date.now(), + }, +}); + +/** + * 비디오 로딩 성공 상태로 설정 + * @param {string} videoId - 비디오 ID + * @param {string} displayMode - 화면 모드 + */ +export const setPlaybackSuccess = (videoId, displayMode = DISPLAY_STATUS.VISIBLE) => ({ + type: types.SET_PLAYBACK_SUCCESS, + payload: { + playback: PLAYBACK_STATUS.LOAD_SUCCESS, + display: displayMode, + videoId, + loadingProgress: 100, + loadingError: null, + lastUpdate: Date.now(), + }, +}); + +/** + * 비디오 로딩 에러 상태로 설정 + * @param {string} videoId - 비디오 ID + * @param {object} error - 에러 정보 + */ +export const setPlaybackError = (videoId, error) => ({ + type: types.SET_VIDEO_ERROR, + payload: { + playback: PLAYBACK_STATUS.LOAD_ERROR, + display: DISPLAY_STATUS.VISIBLE, + videoId, + loadingProgress: 0, + loadingError: error, + lastUpdate: Date.now(), + }, +}); + +/** + * 비디오 재생 상태로 설정 + * @param {string} videoId - 비디오 ID + * @param {string} displayMode - 화면 모드 + */ +export const setPlaybackPlaying = (videoId, displayMode = DISPLAY_STATUS.FULLSCREEN) => ({ + type: types.SET_VIDEO_PLAYING, + payload: { + playback: PLAYBACK_STATUS.PLAYING, + display: displayMode, + videoId, + loadingProgress: 100, + loadingError: null, + lastUpdate: Date.now(), + }, +}); + +/** + * 비디오 정지 상태로 설정 + */ +export const setVideoStopped = () => ({ + type: types.SET_VIDEO_STOPPED, + payload: { + playback: PLAYBACK_STATUS.NOT_PLAYING, + display: DISPLAY_STATUS.HIDDEN, + videoId: null, + loadingProgress: 0, + loadingError: null, + lastUpdate: Date.now(), + }, +}); + +/** + * 비디오 버퍼링 상태로 설정 + * @param {string} videoId - 비디오 ID + */ +export const setPlaybackBuffering = (videoId) => ({ + type: types.SET_PLAYBACK_BUFFERING, + payload: { + playback: PLAYBACK_STATUS.BUFFERING, + videoId, + lastUpdate: Date.now(), + }, +}); + +/** + * 최소화된 상태로 비디오 재생 + * @param {string} videoId - 비디오 ID + */ +export const setVideoMinimizedPlaying = (videoId) => ({ + type: types.SET_VIDEO_MINIMIZED_PLAYING, + payload: { + playback: PLAYBACK_STATUS.PLAYING, + display: DISPLAY_STATUS.MINIMIZED, + videoId, + loadingProgress: 100, + loadingError: null, + lastUpdate: Date.now(), + }, +}); + +/** + * 화면 상태만 변경하는 액션들 + */ +export const setDisplayHidden = () => ({ + type: types.SET_DISPLAY_HIDDEN, + payload: { + display: DISPLAY_STATUS.HIDDEN, + lastUpdate: Date.now(), + }, +}); + +export const setDisplayVisible = () => ({ + type: types.SET_DISPLAY_VISIBLE, + payload: { + display: DISPLAY_STATUS.VISIBLE, + lastUpdate: Date.now(), + }, +}); + +export const setDisplayMinimized = () => ({ + type: types.SET_DISPLAY_MINIMIZED, + payload: { + display: DISPLAY_STATUS.MINIMIZED, + lastUpdate: Date.now(), + }, +}); + +export const setDisplayFullscreen = () => ({ + type: types.SET_DISPLAY_FULLSCREEN, + payload: { + display: DISPLAY_STATUS.FULLSCREEN, + lastUpdate: Date.now(), + }, +}); diff --git a/com.twin.app.shoptime/src/reducers/playReducer.js b/com.twin.app.shoptime/src/reducers/playReducer.js index 579a5ea6..62e62914 100644 --- a/com.twin.app.shoptime/src/reducers/playReducer.js +++ b/com.twin.app.shoptime/src/reducers/playReducer.js @@ -1,14 +1,24 @@ import { types } from '../actions/actionTypes'; +import { PLAYBACK_STATUS, DISPLAY_STATUS } from '../actions/playActions'; const initialState = { subTitleBlobs: {}, chatData: null, videoPlayState: { + // 기존 상태들 유지 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, // 마지막 업데이트 시간 }, }; @@ -59,6 +69,193 @@ export const playReducer = (state = initialState, action) => { }; } + // 🔽 [251116] 새로운 비디오 상태 관리 시스템 - 재생 상태 액션들 + case types.SET_PLAYBACK_LOADING: { + return { + ...state, + videoPlayState: { + ...state.videoPlayState, + playback: PLAYBACK_STATUS.LOADING, + loadingProgress: 0, + loadingError: null, + lastUpdate: Date.now(), + }, + }; + } + + case types.SET_PLAYBACK_SUCCESS: { + return { + ...state, + videoPlayState: { + ...state.videoPlayState, + playback: PLAYBACK_STATUS.LOAD_SUCCESS, + loadingProgress: 100, + loadingError: null, + lastUpdate: Date.now(), + }, + }; + } + + case types.SET_PLAYBACK_ERROR: { + return { + ...state, + videoPlayState: { + ...state.videoPlayState, + playback: PLAYBACK_STATUS.LOAD_ERROR, + loadingProgress: 0, + loadingError: action.payload, + lastUpdate: Date.now(), + }, + }; + } + + case types.SET_PLAYBACK_PLAYING: { + return { + ...state, + videoPlayState: { + ...state.videoPlayState, + playback: PLAYBACK_STATUS.PLAYING, + isPlaying: true, + isPaused: false, + loadingProgress: 100, + loadingError: null, + lastUpdate: Date.now(), + }, + }; + } + + case types.SET_PLAYBACK_NOT_PLAYING: { + return { + ...state, + videoPlayState: { + ...state.videoPlayState, + playback: PLAYBACK_STATUS.NOT_PLAYING, + isPlaying: false, + isPaused: true, + loadingProgress: 0, + lastUpdate: Date.now(), + }, + }; + } + + case types.SET_PLAYBACK_BUFFERING: { + return { + ...state, + videoPlayState: { + ...state.videoPlayState, + playback: PLAYBACK_STATUS.BUFFERING, + loadingError: null, + lastUpdate: Date.now(), + }, + }; + } + + // 🔽 [251116] 새로운 비디오 상태 관리 시스템 - 화면 상태 액션들 + case types.SET_DISPLAY_HIDDEN: { + return { + ...state, + videoPlayState: { + ...state.videoPlayState, + display: DISPLAY_STATUS.HIDDEN, + lastUpdate: Date.now(), + }, + }; + } + + case types.SET_DISPLAY_VISIBLE: { + return { + ...state, + videoPlayState: { + ...state.videoPlayState, + display: DISPLAY_STATUS.VISIBLE, + lastUpdate: Date.now(), + }, + }; + } + + case types.SET_DISPLAY_MINIMIZED: { + return { + ...state, + videoPlayState: { + ...state.videoPlayState, + display: DISPLAY_STATUS.MINIMIZED, + lastUpdate: Date.now(), + }, + }; + } + + case types.SET_DISPLAY_FULLSCREEN: { + return { + ...state, + videoPlayState: { + ...state.videoPlayState, + display: DISPLAY_STATUS.FULLSCREEN, + lastUpdate: Date.now(), + }, + }; + } + + // 🔽 [251116] 복합 상태 액션들 + case types.SET_VIDEO_LOADING: { + return { + ...state, + videoPlayState: { + ...state.videoPlayState, + ...action.payload, + isPlaying: false, + isPaused: true, + }, + }; + } + + case types.SET_VIDEO_PLAYING: { + return { + ...state, + videoPlayState: { + ...state.videoPlayState, + ...action.payload, + isPlaying: true, + isPaused: false, + }, + }; + } + + case types.SET_VIDEO_STOPPED: { + return { + ...state, + videoPlayState: { + ...state.videoPlayState, + ...action.payload, + isPlaying: false, + isPaused: true, + }, + }; + } + + case types.SET_VIDEO_MINIMIZED_PLAYING: { + return { + ...state, + videoPlayState: { + ...state.videoPlayState, + ...action.payload, + isPlaying: true, + isPaused: false, + }, + }; + } + + case types.SET_VIDEO_ERROR: { + return { + ...state, + videoPlayState: { + ...state.videoPlayState, + ...action.payload, + isPlaying: false, + isPaused: true, + }, + }; + } + default: return state; } 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 c562fc03..7b7720b5 100644 --- a/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx +++ b/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx @@ -87,8 +87,8 @@ export default function RandomUnit({ const isVideoTransitionLocked = useSelector((state) => state.home.videoTransitionLocked); const timerRef = useRef(); - const keepTimerOnBlurRef = useRef(false); const hasAutoPlayStartedRef = useRef(false); + const hasPlaybackStartedRef = useRef(false); const isDefaultAutoPlayTarget = defaultFocus === spotlightId; const bannerDataRef = useRef(bannerData); const randomDataRef = useRef( @@ -105,6 +105,7 @@ export default function RandomUnit({ const handleStartVideo = useCallback( (videoProps) => { dispatch(setVideoTransitionLock(true)); + hasPlaybackStartedRef.current = true; dispatch(startVideoPlayerNew(videoProps)); }, [dispatch] @@ -115,6 +116,14 @@ export default function RandomUnit({ dispatch(setVideoTransitionLock(false)); }, [dispatch]); + useEffect(() => { + if (currentVideoBannerId === spotlightId) { + hasPlaybackStartedRef.current = true; + } else { + hasPlaybackStartedRef.current = false; + } + }, [currentVideoBannerId, spotlightId]); + useEffect(() => { if (isVideoTransitionLocked && isCurrentBannerVideoPlaying) { dispatch(setVideoTransitionLock(false)); @@ -595,11 +604,10 @@ export default function RandomUnit({ }, [randomData]); useEffect(() => { - if (isFocused && !videoError) { + if (isFocused && !videoError && !hasPlaybackStartedRef.current) { if (timerRef.current) { clearTimeout(timerRef.current); } - keepTimerOnBlurRef.current = isDefaultAutoPlayTarget && !hasAutoPlayStartedRef.current; timerRef.current = setTimeout(() => { handleStartVideo({ bannerId: spotlightId, @@ -617,30 +625,27 @@ export default function RandomUnit({ if (isDefaultAutoPlayTarget) { hasAutoPlayStartedRef.current = true; } - keepTimerOnBlurRef.current = false; timerRef.current = null; }, 1000); } if (!isFocused) { setVideoError(false); - if (timerRef.current && !keepTimerOnBlurRef.current) { + if (timerRef.current && !hasPlaybackStartedRef.current) { clearTimeout(timerRef.current); timerRef.current = null; } } return () => { - if (timerRef.current) { + if (timerRef.current && !hasPlaybackStartedRef.current) { clearTimeout(timerRef.current); timerRef.current = null; } - keepTimerOnBlurRef.current = false; }; }, [ isFocused, videoError, isHorizontal, randomData, - dispatch, isDefaultAutoPlayTarget, spotlightId, handleStartVideo,