[251116] feat: playeReducer , playActions.js에 videoState 추가
🕐 커밋 시간: 2025. 11. 16. 17:28:35 📊 변경 통계: • 총 파일: 5개 • 추가: +398줄 • 삭제: -8줄 📁 추가된 파일: + com.twin.app.shoptime/[251116]_video_state_management_design.md 📝 수정된 파일: ~ com.twin.app.shoptime/src/actions/actionTypes.js ~ com.twin.app.shoptime/src/actions/playActions.js ~ com.twin.app.shoptime/src/reducers/playReducer.js ~ com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx 🔧 함수 변경 내용: 📄 com.twin.app.shoptime/src/actions/playActions.js (javascript): ✅ Added: returnToPreview() 🔄 Modified: finishModalVideoForce(), shrinkVideoTo1px(), resumePlayerControl() 📄 com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx (javascript): 🔄 Modified: SpotlightContainerDecorator() 📄 com.twin.app.shoptime/[251116]_video_state_management_design.md (md파일): ✅ Added: curry(), dispatch(), useSelector() 🔧 주요 변경 내용: • 타입 시스템 안정성 강화 • 핵심 비즈니스 로직 개선 • 개발 문서 및 가이드 개선
This commit is contained in:
220
com.twin.app.shoptime/[251116]_video_state_management_design.md
Normal file
220
com.twin.app.shoptime/[251116]_video_state_management_design.md
Normal file
@@ -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 <LoadingSpinner progress={progress} />;
|
||||
}
|
||||
|
||||
if (playback === PLAYBACK_STATUS.LOAD_ERROR) {
|
||||
return <ErrorMessage error={error} onRetry={handleRetry} />;
|
||||
}
|
||||
|
||||
if (playback === PLAYBACK_STATUS.BUFFERING) {
|
||||
return <BufferingIndicator />;
|
||||
}
|
||||
|
||||
if (playback === PLAYBACK_STATUS.PLAYING && display === DISPLAY_STATUS.FULLSCREEN) {
|
||||
return <VideoPlayer videoId={videoState.videoId} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
```
|
||||
|
||||
## 구현 순서
|
||||
|
||||
1. [ ] playerActions.js에 상수 및 액션 함수들 추가
|
||||
2. [ ] playerReducer.js에 초기 상태 및 핸들러들 추가
|
||||
3. [ ] PlayerPanel.jsx에서 새로운 상태 시스템으로 전환
|
||||
4. [ ] VideoPlayer.js에서 새로운 상태 시스템으로 전환
|
||||
5. [ ] 테스트 및 검증
|
||||
|
||||
## 장점
|
||||
|
||||
1. **정밀한 상태 제어**: 재생 상태와 화면 상태를 별도로 관리
|
||||
2. **명확한 상태 의미**: 각 상태가 명확한 의미를 가짐
|
||||
3. **확장성**: 새로운 상태 추가가 용이
|
||||
4. **유지보수성**: 기존 코드 영향 최소화
|
||||
5. **재사용성**: 다른 컴포넌트에서도 활용 가능
|
||||
|
||||
## 주의사항
|
||||
|
||||
- 기존 videoPlayReducer와 충돌하지 않도록 주의
|
||||
- PlayerPanel과 VideoPlayer.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',
|
||||
|
||||
// 🔽 [추가] 플레이 제어 매니저 액션 타입
|
||||
/**
|
||||
* 홈 화면 배너의 비디오 재생 제어를 위한 액션 타입.
|
||||
|
||||
@@ -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(),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user