Files
shoptime/com.twin.app.shoptime/src/actions/playActions.js
optrader 9f3cd62549 [251026] feat: modal 모드에서 비디오 1px 축소/복구 기능 구현
- PlayerPanel useEffect에서 shouldShrinkTo1px 플래그로 인라인 스타일 제거
- shrinkVideoTo1px/expandVideoFrom1px 액션 추가 (배너 정보 playerState에 저장)
- PlayerPanel.module.less에 .shrinkTo1px 클래스 추가
- HomePanel onScroll에서 스크롤 방향 감지하여 축소/확대 제어
- RandomUnit onBlur에서의 축소 로직은 주석 처리 (스크롤로 제어)
2025-10-26 21:44:13 +09:00

545 lines
17 KiB
JavaScript

import Spotlight from '@enact/spotlight';
import { URLS } from '../api/apiConfig';
import { TAxios } from '../api/TAxios';
import { panel_names } from '../utils/Config';
import { types } from './actionTypes';
import { popPanel, pushPanel, updatePanel } from './panelActions';
//yhcho
/*
dispatch(startVideoPreview({
patnrId: randomData.patnrId,
showId: randomData.showId,
shptmBanrTpNm: randomData.shptmBanrTpNm,
lgCatCd: randomData.lgCatCd,
modal: true,
modalContainerRef: videoModalContainerRef.current, //to calc width, height, left, top
modalClassName:css.videoModal
}));
modalClassName: modal more class info and checking it's launched from preview mode
*/
let startVideoTimer = null;
//start modal mode
//start Full -> modal mode
let startVideoFocusTimer = null;
export const startVideoPlayer =
({ modal, modalContainerId, modalClassName, spotlightDisable, useNewPlayer, ...rest }) =>
(dispatch, getState) => {
const panels = getState().panels.panels;
const topPanel = panels[panels.length - 1];
let panelWorkingAction = pushPanel;
// const panelName = useNewPlayer ? panel_names.PLAYER_PANEL_NEW : panel_names.PLAYER_PANEL;
const panelName = panel_names.PLAYER_PANEL;
if (topPanel && topPanel.name === panelName) {
panelWorkingAction = updatePanel;
}
dispatch(
panelWorkingAction(
{
name: panelName,
panelInfo: {
modal,
modalContainerId,
modalClassName,
...rest,
},
},
true
)
);
if (modal && modalContainerId && !spotlightDisable) {
Spotlight.setPointerMode(false);
startVideoFocusTimer = setTimeout(() => {
Spotlight.focus(modalContainerId);
}, 0);
}
};
export const startVideoPlayerNew =
({ modal, modalContainerId, modalClassName, spotlightDisable, useNewPlayer, bannerId, ...rest }) =>
(dispatch, getState) => {
const panels = getState().panels.panels;
const topPanel = panels[panels.length - 1];
let panelWorkingAction = pushPanel;
// const panelName = useNewPlayer ? panel_names.PLAYER_PANEL_NEW : panel_names.PLAYER_PANEL;
const panelName = panel_names.PLAYER_PANEL;
if (topPanel && topPanel.name === panelName) {
panelWorkingAction = updatePanel;
}
// playerState 업데이트: 기존 playerState와 새 데이터 병합
const currentPlayerState = topPanel?.panelInfo?.playerState || {};
// 같은 배너에서 이미 비디오가 재생 중이면 return (중복 재생 방지)
if (currentPlayerState.currentBannerId === bannerId) {
return;
}
const newPlayerState = {
...currentPlayerState,
currentBannerId: bannerId,
};
dispatch(
panelWorkingAction(
{
name: panelName,
panelInfo: {
modal,
modalContainerId,
modalClassName,
playerState: newPlayerState,
...rest,
},
},
true
)
);
if (modal && modalContainerId && !spotlightDisable) {
Spotlight.setPointerMode(false);
startVideoFocusTimer = setTimeout(() => {
Spotlight.focus(modalContainerId);
}, 0);
}
};
export const finishVideoPreview = () => (dispatch, getState) => {
const panels = getState().panels.panels;
const topPanel = panels[panels.length - 1];
if (topPanel && topPanel.name === panel_names.PLAYER_PANEL && topPanel.panelInfo.modal) {
if (startVideoFocusTimer) {
clearTimeout(startVideoFocusTimer);
startVideoFocusTimer = null;
}
dispatch(popPanel());
}
};
export const finishModalVideoForce = () => (dispatch, getState) => {
const panels = getState().panels.panels;
// modal PlayerPanel이 존재하는지 확인 (스택 어디에 있든)
const hasModalPlayerPanel = panels.some(
(panel) => panel.name === panel_names.PLAYER_PANEL && panel.panelInfo?.modal
);
if (hasModalPlayerPanel) {
if (startVideoFocusTimer) {
clearTimeout(startVideoFocusTimer);
startVideoFocusTimer = null;
}
// panelName을 지정하면 스택 어디에 있든 해당 패널을 제거
dispatch(popPanel(panel_names.PLAYER_PANEL));
}
};
// 모달 비디오를 일시정지 (패널은 유지)
export const pauseModalVideo = () => (dispatch, getState) => {
const panels = getState().panels.panels;
// modal PlayerPanel 찾기
const modalPlayerPanel = panels.find(
(panel) => panel.name === panel_names.PLAYER_PANEL && panel.panelInfo?.modal
);
if (modalPlayerPanel) {
console.log('[pauseModalVideo] Pausing modal video');
dispatch(
updatePanel({
name: panel_names.PLAYER_PANEL,
panelInfo: {
...modalPlayerPanel.panelInfo,
isPaused: true, // 일시정지 플래그 추가
},
})
);
}
};
// 모달 비디오를 재생 (일시정지 해제)
export const resumeModalVideo = () => (dispatch, getState) => {
const panels = getState().panels.panels;
// modal PlayerPanel 찾기
const modalPlayerPanel = panels.find(
(panel) => panel.name === panel_names.PLAYER_PANEL && panel.panelInfo?.modal
);
if (modalPlayerPanel && modalPlayerPanel.panelInfo?.isPaused) {
console.log('[resumeModalVideo] Resuming modal video');
dispatch(
updatePanel({
name: panel_names.PLAYER_PANEL,
panelInfo: {
...modalPlayerPanel.panelInfo,
isPaused: false, // 일시정지 해제
},
})
);
}
};
// 전체화면 비디오를 일시정지 (패널은 유지)
export const pauseFullscreenVideo = () => (dispatch, getState) => {
const panels = getState().panels.panels;
// 전체화면 PlayerPanel 찾기 (modal이 false인 패널)
const fullscreenPlayerPanel = panels.find(
(panel) => panel.name === panel_names.PLAYER_PANEL && !panel.panelInfo?.modal
);
if (fullscreenPlayerPanel) {
dispatch(
updatePanel({
name: panel_names.PLAYER_PANEL,
panelInfo: {
...fullscreenPlayerPanel.panelInfo,
isPaused: true, // 일시정지 플래그 추가
},
})
);
}
};
// 전체화면 비디오를 재생 (일시정지 해제)
export const resumeFullscreenVideo = () => (dispatch, getState) => {
const panels = getState().panels.panels;
// console.log('[BgVideo] resumeFullscreenVideo called - panels:', {
// panelsCount: panels?.length,
// panels: panels?.map(p => ({ name: p.name, modal: p.panelInfo?.modal, isPaused: p.panelInfo?.isPaused }))
// });
// 전체화면 PlayerPanel 찾기 (modal이 false인 패널)
const fullscreenPlayerPanel = panels.find(
(panel) => panel.name === panel_names.PLAYER_PANEL && !panel.panelInfo?.modal
);
// console.log('[BgVideo] resumeFullscreenVideo - fullscreenPlayerPanel found:', !!fullscreenPlayerPanel);
// console.log('[BgVideo] resumeFullscreenVideo - isPaused:', fullscreenPlayerPanel?.panelInfo?.isPaused);
if (fullscreenPlayerPanel && fullscreenPlayerPanel.panelInfo?.isPaused) {
// console.log('[BgVideo] resumeFullscreenVideo - dispatching updatePanel with isPaused: false');
dispatch(
updatePanel({
name: panel_names.PLAYER_PANEL,
panelInfo: {
...fullscreenPlayerPanel.panelInfo,
isPaused: false, // 일시정지 해제
},
})
);
} else {
console.log('[BgVideo] resumeFullscreenVideo - Not resuming (not found or not paused)');
}
};
// 모달 비디오를 1px로 축소 (배너 정보 저장)
export const shrinkVideoTo1px = () => (dispatch, getState) => {
const panels = getState().panels.panels;
// modal PlayerPanel 찾기
const modalPlayerPanel = panels.find(
(panel) => panel.name === panel_names.PLAYER_PANEL && panel.panelInfo?.modal
);
if (modalPlayerPanel) {
const panelInfo = modalPlayerPanel.panelInfo;
console.log('[shrinkVideoTo1px] Shrinking modal video to 1px');
// 축소 전 배너 정보를 playerState에 저장
const updatedPlayerState = {
...(panelInfo.playerState || {}),
shrinkInfo: {
// 복구 시 필요한 정보
modalContainerId: panelInfo.modalContainerId,
modalClassName: panelInfo.modalClassName,
modalStyle: panelInfo.modalStyle,
modalScale: panelInfo.modalScale,
currentBannerId: panelInfo.playerState?.currentBannerId,
patnrId: panelInfo.patnrId,
showId: panelInfo.showId,
shptmBanrTpNm: panelInfo.shptmBanrTpNm,
lgCatCd: panelInfo.lgCatCd,
},
};
dispatch(
updatePanel({
name: panel_names.PLAYER_PANEL,
panelInfo: {
...panelInfo,
shouldShrinkTo1px: true, // 축소 플래그 설정
playerState: updatedPlayerState,
},
})
);
}
};
// 축소된 모달 비디오를 원래 크기로 복구
export const expandVideoFrom1px = () => (dispatch, getState) => {
const panels = getState().panels.panels;
// 축소된 modal PlayerPanel 찾기
const shrunkModalPlayerPanel = panels.find(
(panel) => panel.name === panel_names.PLAYER_PANEL && panel.panelInfo?.modal && panel.panelInfo?.shouldShrinkTo1px
);
if (shrunkModalPlayerPanel) {
const panelInfo = shrunkModalPlayerPanel.panelInfo;
const shrinkInfo = panelInfo.playerState?.shrinkInfo;
console.log('[expandVideoFrom1px] Expanding modal video from 1px', shrinkInfo);
dispatch(
updatePanel({
name: panel_names.PLAYER_PANEL,
panelInfo: {
...panelInfo,
shouldShrinkTo1px: false, // 축소 플래그 해제
// 저장된 정보로 복구
...(shrinkInfo && {
modalContainerId: shrinkInfo.modalContainerId,
modalClassName: shrinkInfo.modalClassName,
modalStyle: shrinkInfo.modalStyle,
modalScale: shrinkInfo.modalScale,
}),
},
})
);
}
};
// 채팅 로그 가져오기 IF-LGSP-371
export const getChatLog =
({ patnrId, showId }) =>
(dispatch, getState) => {
const onSuccess = (response) => {
console.log('getChatLog onSuccess', response.data);
dispatch({
type: types.GET_CHAT_LOG,
payload: response.data.data,
});
};
const onFail = (error) => {
console.error('getChatLog onFail', error);
};
TAxios(dispatch, getState, 'get', URLS.CHAT_LOG, { patnrId, showId }, {}, onSuccess, onFail);
};
// VOD 자막 가져오기 IF-LGSP-072
export const getSubTitle =
({ showSubtitleUrl }) =>
(dispatch, getState) => {
const onSuccess = (response) => {
console.log('getSubTitle onSuccess', response.data);
dispatch({
type: types.GET_SUBTITLE,
payload: { url: showSubtitleUrl, data: response.data.data },
});
};
const onFail = (error) => {
console.error('getSubTitle onFail', error);
dispatch({
type: types.GET_SUBTITLE,
payload: { url: showSubtitleUrl, data: 'Error' },
});
};
if (!getState().play.subTitleBlobs[showSubtitleUrl]) {
TAxios(dispatch, getState, 'get', URLS.SUBTITLE, { showSubtitleUrl }, {}, onSuccess, onFail);
} else {
console.log("playActions getSubTitle no Nothing it's exist", showSubtitleUrl);
}
};
export const CLEAR_PLAYER_INFO = () => ({
type: types.CLEAR_PLAYER_INFO,
});
/**
* 비디오 재생 상태를 Redux에 업데이트합니다.
* @param {Object} playState - 업데이트할 재생 상태
* @param {boolean} playState.isPlaying - 재생 중인지 여부
* @param {boolean} playState.isPaused - 일시정지 상태인지 여부
* @param {number} playState.currentTime - 현재 재생 시간(초)
* @param {number} playState.duration - 전체 비디오 길이(초)
* @param {number} playState.playbackRate - 재생 속도
*/
export const updateVideoPlayState = (playState) => ({
type: types.UPDATE_VIDEO_PLAY_STATE,
payload: playState,
});
/* 🔽 [추가] 새로운 '플레이 제어 매니저' 액션들 */
/**
* 비디오 재생 제어권을 요청합니다.
* 컴포넌트는 이 액션을 통해 중앙 매니저에게 재생을 '요청'합니다.
* @param {string} ownerId - 제어권을 요청하는 컴포넌트의 고유 ID (예: 'banner0_persistent')
* @param {object} videoInfo - 재생할 비디오 정보 (url, id 등)
*/
export const requestPlayControl = (ownerId, videoInfo) => (dispatch, getState) => {
const { playerControl } = getState().home;
const currentOwnerId = playerControl.ownerId;
if (currentOwnerId === ownerId) return; // 이미 제어권 소유
if (currentOwnerId) {
// 현재 제어중인 컴포넌트가 영구재생 배너이면 '일시정지'
if (currentOwnerId === 'banner0_persistent') {
dispatch(pausePlayerControl());
} else {
// 다른 미리보기라면 완전히 숨김
dispatch(releasePlayControl(currentOwnerId, true));
}
}
// 1. 매니저 상태 업데이트
dispatch({ type: types.SET_PLAYER_CONTROL, payload: { ownerId } });
// 2. 공유 PlayerPanel의 상태 업데이트
dispatch(
updatePanel({
name: panel_names.PLAYER_PANEL,
panelInfo: {
isHidden: false,
modal: true,
...videoInfo,
},
})
);
};
/**
* 비디오 재생 제어권을 해제하고, 필요시 영구재생 비디오를 복원합니다.
* @param {string} ownerId - 제어권을 해제하는 컴포넌트의 고유 ID
* @param {boolean} fromPreemption - 다른 요청에 의해 강제로 중단되었는지 여부
*/
export const releasePlayControl =
(ownerId, fromPreemption = false) =>
(dispatch, getState) => {
const { playerControl } = getState().home;
if (fromPreemption || playerControl.ownerId === ownerId) {
// 1. 공유 PlayerPanel을 다시 숨김
dispatch(
updatePanel({
name: panel_names.PLAYER_PANEL,
panelInfo: {
isHidden: true,
},
})
);
// 2. 매니저 상태 업데이트 (현재 소유주 없음)
dispatch({ type: types.CLEAR_PLAYER_CONTROL });
// 3. 만약 '일시정지'된 영구재생 비디오가 있었다면, 제어권을 되돌려주고 다시 재생
if (playerControl.isPaused && playerControl.ownerId === 'banner0_persistent') {
const persistentVideoInfo = {
/* 영구 비디오 정보를 가져오는 로직 (필요시) */
};
dispatch(requestPlayControl('banner0_persistent', persistentVideoInfo));
}
}
};
/**
* 현재 재생 중인 비디오를 '일시정지' 상태로 변경하는 액션.
* 이 함수는 플레이어 패널을 닫지 않고, 단순히 비디오 재생을 멈추는 신호를 보냅니다.
*
* @param {string} ownerId - 비디오 제어권을 가진 컴포넌트의 고유 ID.
*/
// export const pausePlayerControl = (ownerId) => (dispatch, getState) => {
// const { playerControl } = getState().home;
// // 제어권을 가진 컴포넌트가 자신일 경우에만 일시정지
// if (playerControl.ownerId === ownerId) {
// dispatch({
// type: types.PAUSE_PLAYER_CONTROL,
// });
// }
// };
/**
* '일시정지' 상태의 비디오를 다시 재생하는 액션.
*
* @param {string} ownerId - 비디오 제어권을 가진 컴포넌트의 고유 ID.
*/
export const resumePlayerControl = (ownerId) => (dispatch, getState) => {
const { playerControl } = getState().home;
// 제어권을 가진 컴포넌트가 자신이고, 일시정지 상태일 때만 재개
if (playerControl.ownerId === ownerId && playerControl.isPaused) {
dispatch({
type: types.RESUME_PLAYER_CONTROL,
});
}
};
/**
* 공유 PlayerPanel을 전체화면 모드로 전환합니다.
* 이 액션은 어떤 배너에서든 클릭 시 호출됩니다.
*/
export const goToFullScreen = () => (dispatch, getState) => {
// 공유 PlayerPanel의 'modal' 상태를 false로 변경하여 전체화면으로 전환
dispatch(
updatePanel({
name: panel_names.PLAYER_PANEL,
panelInfo: {
modal: false,
isHidden: false, // 혹시 숨겨져 있었다면 보이도록
},
})
);
};
/**
* 영구재생 비디오를 일시정지 상태로 만듭니다. (내부 사용)
*/
export const pausePlayerControl = () => ({
type: types.PAUSE_PLAYER_CONTROL,
});
/**
* 전체화면 플레이어에서 미리보기 상태로 복귀할 때 호출됩니다.
* 중앙 'playerControl' 상태를 확인하여 올바른 위치와 비디오로 복원합니다.
*/
export const returnToPreview = () => (dispatch, getState) => {
const { playerControl } = getState().home;
let targetOwnerId;
let targetVideoInfo;
// 만약 '일시정지'된 영구재생 비디오가 있다면, 무조건 그 비디오로 복귀하는 것이 최우선
if (playerControl.isPaused) {
targetOwnerId = 'banner0_persistent';
// targetVideoInfo = ... (0번 배너의 비디오 정보를 가져오는 로직)
} else {
// 그렇지 않다면, 전체화면으로 가기 직전의 소유주(ownerId)에게로 복귀
targetOwnerId = playerControl.ownerId;
// targetVideoInfo = ... (해당 ownerId의 비디오 정보를 가져오는 로직)
}
// 매니저에게 해당 타겟으로 재생을 다시 요청
if (targetOwnerId) {
dispatch(requestPlayControl(targetOwnerId, targetVideoInfo));
} else {
// 돌아갈 곳이 없으면 그냥 플레이어를 닫음
dispatch(finishVideoPreview());
}
};