Files
shoptime/com.twin.app.shoptime/src/actions/playActions.js

1379 lines
41 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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';
import { createDebugHelpers } from '../utils/debug';
// 디버그 헬퍼 설정
const DEBUG_MODE = false;
const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE);
// 🔽 [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({
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 clearAllVideoTimers = () => {
if (startVideoFocusTimer) {
clearTimeout(startVideoFocusTimer);
startVideoFocusTimer = null;
dlog('[playActions] startVideoFocusTimer cleared');
}
if (startVideoTimer) {
clearTimeout(startVideoTimer);
startVideoTimer = null;
dlog('[playActions] startVideoTimer cleared');
}
};
export const startVideoPlayer =
({
modal,
modalContainerId,
modalClassName,
spotlightDisable,
useNewPlayer,
videoId,
showUrl,
...rest
}) =>
(dispatch, getState) => {
const caller = new Error().stack?.split('\n')[2]?.trim();
console.log('[PTRACE-SP] startVideoPlayer call', {
modal,
modalContainerId,
modalClassName,
videoId,
showUrl,
caller,
});
dlog(
'[startVideoPlayer] ✅ START - videoId:',
videoId,
', showUrl:',
showUrl,
', modal:',
modal
);
// 🔽 [251116] 즉시 로딩 상태 설정
const videoIdentifier = videoId || showUrl;
if (videoIdentifier) {
const displayMode = modal ? DISPLAY_STATUS.VISIBLE : DISPLAY_STATUS.FULLSCREEN;
dlog(
'[startVideoPlayer] 📌 Setting playback loading - identifier:',
videoIdentifier,
', displayMode:',
displayMode
);
dispatch(setPlaybackLoading(videoIdentifier, displayMode));
} else {
dlog(
'[startVideoPlayer] ⚠️ No videoIdentifier provided (videoId and showUrl are both missing)'
);
}
const panels = getState().panels.panels;
const existingPlayerPanel = panels.find((p) => p.name === panel_names.PLAYER_PANEL);
let panelWorkingAction = pushPanel;
// 기존 PlayerPanel이 어디든 있으면 완전히 초기화: 타이머 정리 후 pop → 새로 push
if (existingPlayerPanel) {
console.log('[PTRACE-SP] startVideoPlayer: popping existing player before push', {
stack: panels.map((p) => p.name),
});
dlog('[startVideoPlayer] 🔄 Resetting existing PLAYER_PANEL before start');
clearAllVideoTimers();
dispatch(popPanel(panel_names.PLAYER_PANEL));
} else {
dlog(
'[startVideoPlayer] 📊 No existing PLAYER_PANEL - panelsCount:',
panels.length
);
}
dispatch(
panelWorkingAction(
{
name: panel_names.PLAYER_PANEL,
panelInfo: {
modal,
modalContainerId,
modalClassName,
videoId, // videoId 추가하여 PlayerPanel에서 사용 가능
showUrl, // showUrl 추가하여 PlayerPanel에서 사용 가능
...rest,
},
},
true
)
);
dlog('[startVideoPlayer] ✨ Panel action dispatched');
// [COMMENTED OUT] 비디오 재생 시 강제 포커스 이동 비활성화
// if (modal && modalContainerId && !spotlightDisable) {
// console.log('[startVideoPlayer] 🎯 Setting Spotlight focus - containerId:', modalContainerId);
// Spotlight.setPointerMode(false);
// startVideoFocusTimer = setTimeout(() => {
// console.log('[startVideoPlayer] 🔍 Spotlight.focus called');
// Spotlight.focus(modalContainerId);
// }, 0);
// } else {
// console.log('[startVideoPlayer] ⏭️ Spotlight focus skipped - modal:', modal, ', modalContainerId:', !!modalContainerId, ', spotlightDisable:', spotlightDisable);
// }
dlog('[startVideoPlayer] ✅ END');
};
// 중복 재생 방지: 정말 동일한 요청인지 확인
// 같은 배너 + 같은 modal 상태 + 같은 위치 = 정말 동일한 요청이므로 skip
const shouldSkipVideoPlayback = (
currentPanelInfo,
newModalState,
newModalContainerId,
newBannerId
) => {
if (!currentPanelInfo) return false;
const currentBannerId = currentPanelInfo.playerState?.currentBannerId;
const currentModal = currentPanelInfo.modal;
const currentModalContainerId = currentPanelInfo.modalContainerId;
return (
currentBannerId === newBannerId &&
currentModal === newModalState &&
currentModalContainerId === newModalContainerId
);
};
export const startVideoPlayerNew =
({
modal,
modalContainerId,
modalClassName,
spotlightDisable,
useNewPlayer,
bannerId,
videoId,
showUrl,
...rest
}) =>
(dispatch, getState) => {
const caller = new Error().stack?.split('\n')[2]?.trim();
console.log('[PTRACE-SPN] startVideoPlayerNew call', {
bannerId,
modal,
modalContainerId,
modalClassName,
videoId,
showUrl,
caller,
});
dlog(
'[startVideoPlayerNew] *** ✅ START - bannerId:',
bannerId,
', videoId:',
videoId,
', showUrl:',
showUrl,
', modal:',
modal
);
// 🔽 [251116] 즉시 로딩 상태 설정
const videoIdentifier = videoId || showUrl || bannerId;
if (videoIdentifier) {
const displayMode = modal ? DISPLAY_STATUS.VISIBLE : DISPLAY_STATUS.FULLSCREEN;
dlog(
'[startVideoPlayerNew] *** 📌 Setting playback loading - identifier:',
videoIdentifier,
', displayMode:',
displayMode
);
dispatch(setPlaybackLoading(videoIdentifier, displayMode));
} else {
dlog('[startVideoPlayerNew] *** ⚠️ No videoIdentifier provided');
}
const panels = getState().panels.panels;
const existingPlayerPanel = panels.find((p) => p.name === panel_names.PLAYER_PANEL);
let panelWorkingAction = pushPanel;
let shouldCheckDuplicate = true;
// 기존 PlayerPanel이 있으면 완전히 초기화: 타이머 정리 후 pop → 새로 push
if (existingPlayerPanel) {
console.log('[PTRACE-SPN] popping existing player before push', {
stack: panels.map((p) => p.name),
});
dlog('[startVideoPlayerNew] *** 🔄 Resetting existing PLAYER_PANEL before start');
clearAllVideoTimers();
dispatch(popPanel(panel_names.PLAYER_PANEL));
shouldCheckDuplicate = false;
}
const topPanel = panels[panels.length - 1];
dlog(
'[startVideoPlayerNew] *** 📊 Panel state - panelsCount:',
panels.length,
', topPanelName:',
topPanel?.name
);
let currentPanelInfo = topPanel?.panelInfo || {};
let currentPlayerState = currentPanelInfo.playerState || {};
if (!existingPlayerPanel && topPanel && topPanel.name === panel_names.PLAYER_PANEL) {
panelWorkingAction = updatePanel;
dlog('[startVideoPlayerNew] *** 📋 Current PLAYER_PANEL panelInfo:', topPanel.panelInfo);
}
// 중복 실행 방지: 같은 배너 + 같은 modal 상태/컨테이너 + 같은 URL이면 skip
if (shouldCheckDuplicate) {
const isSameBanner = currentPlayerState.currentBannerId === bannerId;
const isSameModalType = currentPanelInfo.modal === modal;
const isSameContainer = currentPanelInfo.modalContainerId === modalContainerId;
const isSameShowUrl = currentPanelInfo.showUrl === showUrl;
const isSameVideoId = currentPanelInfo.videoId === videoId;
dlog(
'[startVideoPlayerNew] *** 🔍 Duplicate check - isSameBanner:',
isSameBanner,
', isSameModalType:',
isSameModalType,
', isSameContainer:',
isSameContainer,
', isSameShowUrl:',
isSameShowUrl,
', isSameVideoId:',
isSameVideoId
);
if (isSameBanner && isSameModalType && isSameContainer && isSameShowUrl && isSameVideoId) {
dlog('[startVideoPlayerNew] *** ⏭️ SKIPPED - 동일한 요청', {
bannerId,
modal,
modalContainerId,
showUrl,
videoId,
});
return;
}
} else {
// pop으로 초기화한 경우 중복 체크 스킵
currentPanelInfo = {};
currentPlayerState = {};
}
const newPlayerState = {
...currentPlayerState,
currentBannerId: bannerId,
};
dlog('[startVideoPlayerNew] *** 🔄 Player state updated - currentBannerId:', bannerId);
dispatch(
panelWorkingAction(
{
name: panel_names.PLAYER_PANEL,
panelInfo: {
modal,
modalContainerId,
modalClassName,
playerState: newPlayerState,
videoId, // videoId 추가
showUrl, // showUrl 추가
bannerId, // bannerId 추가
...rest,
},
},
true
)
);
dlog(
'[startVideoPlayerNew] *** ✨ Panel action dispatched - action:',
panelWorkingAction === updatePanel ? 'updatePanel' : 'pushPanel'
);
// [COMMENTED OUT] 비디오 재생 시 강제 포커스 이동 비활성화
// if (modal && modalContainerId && !spotlightDisable) {
// console.log('[startVideoPlayerNew] *** 🎯 Setting Spotlight focus - containerId:', modalContainerId);
// Spotlight.setPointerMode(false);
// startVideoFocusTimer = setTimeout(() => {
// console.log('[startVideoPlayerNew] *** 🔍 Spotlight.focus called');
// Spotlight.focus(modalContainerId);
// }, 0);
// } else {
// console.log('[startVideoPlayerNew] *** ⏭️ Spotlight focus skipped - modal:', modal, ', modalContainerId:', !!modalContainerId, ', spotlightDisable:', spotlightDisable);
// }
dlog('[startVideoPlayerNew] *** ✅ END');
};
export const finishVideoPreview = () => (dispatch, getState) => {
if (DEBUG_MODE === true) {
dlog('###-finishVideoPreview');
}
const panels = getState().panels.panels;
const topPanel = panels[panels.length - 1];
if (topPanel && topPanel.name === panel_names.PLAYER_PANEL && topPanel.panelInfo.modal) {
console.log('[PANEL-TRACE] finishVideoPreview: popping modal player', {
topPanelName: topPanel.name,
modal: topPanel.panelInfo.modal,
stack: panels.map((p) => p.name),
panelInfo: topPanel.panelInfo,
});
if (startVideoFocusTimer) {
clearTimeout(startVideoFocusTimer);
startVideoFocusTimer = null;
}
dispatch(popPanel());
}
};
export const finishModalVideoForce = () => (dispatch, getState) => {
const panels = getState().panels.panels;
if (DEBUG_MODE === true) {
dlog('###-finishModalVideoForce');
}
// 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));
}
};
// 모든 PlayerPanel을 강제 제거 (modal과 fullscreen 모두)
export const finishAllVideoForce = () => (dispatch, getState) => {
const panels = getState().panels.panels;
if (DEBUG_MODE === true) {
dlog('###-finishAllVideoForce');
}
// 모든 PlayerPanel이 존재하는지 확인 (스택 어디에 있든)
const hasPlayerPanel = panels.some((panel) => panel.name === panel_names.PLAYER_PANEL);
if (hasPlayerPanel) {
if (startVideoFocusTimer) {
clearTimeout(startVideoFocusTimer);
startVideoFocusTimer = null;
}
// panelName을 지정하면 스택 어디에 있든 해당 패널을 제거
dispatch(popPanel(panel_names.PLAYER_PANEL));
}
};
// 모달 비디오를 일시정지 (패널은 유지)
export const pauseModalVideo = () => (dispatch, getState) => {
const panels = getState().panels.panels;
if (DEBUG_MODE === true) {
dlog('###-pauseModalVideo');
}
// modal PlayerPanel 찾기
const modalPlayerPanel = panels.find(
(panel) => panel.name === panel_names.PLAYER_PANEL && panel.panelInfo?.modal
);
console.log('[Detail-BG] ⏸️ pauseModalVideo - Pausing modal video', {
found: !!modalPlayerPanel,
playerPanelModal: modalPlayerPanel?.panelInfo?.modal,
currentIsPaused: modalPlayerPanel?.panelInfo?.isPaused,
timestamp: Date.now(),
});
if (modalPlayerPanel) {
if (DEBUG_MODE === true) {
dlog('[pauseModalVideo] Pausing modal video');
}
dispatch(
updatePanel({
name: panel_names.PLAYER_PANEL,
panelInfo: {
...modalPlayerPanel.panelInfo,
isPaused: true, // 일시정지 플래그 추가
},
})
);
console.log('[Detail-BG] ✅ pauseModalVideo - Modal video paused successfully', {
timestamp: Date.now(),
});
} else {
console.log('[Detail-BG] ⚠️ pauseModalVideo - No modal PlayerPanel found', {
timestamp: Date.now(),
});
}
};
// 모달 비디오를 재생 (일시정지 해제)
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
);
console.log('[Detail-BG] ▶️ resumeModalVideo - Resuming modal video', {
found: !!modalPlayerPanel,
playerPanelModal: modalPlayerPanel?.panelInfo?.modal,
currentIsPaused: modalPlayerPanel?.panelInfo?.isPaused,
timestamp: Date.now(),
});
if (modalPlayerPanel && modalPlayerPanel.panelInfo?.isPaused) {
if (DEBUG_MODE === true) {
dlog('[resumeModalVideo] Resuming modal video');
}
dispatch(
updatePanel({
name: panel_names.PLAYER_PANEL,
panelInfo: {
...modalPlayerPanel.panelInfo,
isPaused: false, // 일시정지 해제
},
})
);
console.log('[Detail-BG] ✅ resumeModalVideo - Modal video resumed successfully', {
timestamp: Date.now(),
});
} else {
console.log('[Detail-BG] ⚠️ resumeModalVideo - Modal video not paused or panel not found', {
found: !!modalPlayerPanel,
isPaused: modalPlayerPanel?.panelInfo?.isPaused,
timestamp: Date.now(),
});
}
};
// 전체화면 비디오를 일시정지 (패널은 유지)
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
);
console.log('[Detail-BG] ⏸️ pauseFullscreenVideo - Pausing fullscreen video', {
found: !!fullscreenPlayerPanel,
playerPanelModal: fullscreenPlayerPanel?.panelInfo?.modal,
currentIsPaused: fullscreenPlayerPanel?.panelInfo?.isPaused,
timestamp: Date.now(),
});
if (fullscreenPlayerPanel) {
dispatch(
updatePanel({
name: panel_names.PLAYER_PANEL,
panelInfo: {
...fullscreenPlayerPanel.panelInfo,
isPaused: true, // 일시정지 플래그 추가
},
})
);
console.log('[Detail-BG] ✅ pauseFullscreenVideo - Fullscreen video paused successfully', {
timestamp: Date.now(),
});
} else {
console.log('[Detail-BG] ⚠️ pauseFullscreenVideo - No fullscreen PlayerPanel found', {
timestamp: Date.now(),
});
}
};
// 전체화면 비디오를 재생 (일시정지 해제)
export const resumeFullscreenVideo = () => (dispatch, getState) => {
const panels = getState().panels.panels;
// 전체화면 PlayerPanel 찾기 (modal이 false인 패널)
const fullscreenPlayerPanel = panels.find(
(panel) => panel.name === panel_names.PLAYER_PANEL && !panel.panelInfo?.modal
);
console.log('[Detail-BG] ▶️ resumeFullscreenVideo - Resuming fullscreen video', {
found: !!fullscreenPlayerPanel,
playerPanelModal: fullscreenPlayerPanel?.panelInfo?.modal,
currentIsPaused: fullscreenPlayerPanel?.panelInfo?.isPaused,
timestamp: Date.now(),
});
if (fullscreenPlayerPanel && fullscreenPlayerPanel.panelInfo?.isPaused) {
dispatch(
updatePanel({
name: panel_names.PLAYER_PANEL,
panelInfo: {
...fullscreenPlayerPanel.panelInfo,
isPaused: false, // 일시정지 해제
},
})
);
console.log('[Detail-BG] ✅ resumeFullscreenVideo - Fullscreen video resumed successfully', {
timestamp: Date.now(),
});
} else {
console.log('[Detail-BG] ⚠️ resumeFullscreenVideo - Fullscreen video not paused or panel not found', {
found: !!fullscreenPlayerPanel,
isPaused: fullscreenPlayerPanel?.panelInfo?.isPaused,
timestamp: Date.now(),
});
if (DEBUG_MODE === true) {
dlog('[BgVideo] resumeFullscreenVideo - Not resuming (not found or not paused)');
}
}
};
// 모달 비디오를 1px로 축소 (배너 정보 저장)
export const hideModalVideo = () => (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;
// 축소 전 배너 정보를 playerState에 저장
const updatedPlayerState = {
...(panelInfo.playerState || {}),
shrinkInfo: {
// 복구 시 필요한 정보
modalContainerId: panelInfo.modalContainerId,
modalClassName: panelInfo.modalClassName,
modalStyle: panelInfo.modalStyle,
modalScale: panelInfo.modalScale,
// top, left는 따로 저장 (복구 시 재계산하지 않기 위함)
top: panelInfo.modalStyle?.top,
left: panelInfo.modalStyle?.left,
currentBannerId: panelInfo.playerState?.currentBannerId,
patnrId: panelInfo.patnrId,
showId: panelInfo.showId,
shptmBanrTpNm: panelInfo.shptmBanrTpNm,
lgCatCd: panelInfo.lgCatCd,
},
};
// console.log('[HomePanel] hideModalVideo: saving shrinkInfo', {
// shrinkInfo: updatedPlayerState.shrinkInfo,
// modalStyle: panelInfo.modalStyle,
// });
dispatch(
updatePanel({
name: panel_names.PLAYER_PANEL,
panelInfo: {
...panelInfo,
shouldShrinkTo1px: true, // 축소 플래그 설정
playerState: updatedPlayerState,
},
})
);
} else {
if (DEBUG_MODE === true) {
dlog('[HomePanel] hideModalVideo: No modal PlayerPanel found', {
panels: panels.map((p) => ({
name: p.name,
modal: p.panelInfo?.modal,
shouldShrinkTo1px: p.panelInfo?.shouldShrinkTo1px,
})),
});
}
}
};
// 축소된 모달 비디오를 원래 크기로 복구
export const showModalVideo = () => (dispatch, getState) => {
if (DEBUG_MODE === true) {
dlog('[showModalVideo] *** ✅ START');
}
const panels = getState().panels.panels;
if (DEBUG_MODE === true) {
dlog('[showModalVideo] *** 📊 Total panels count:', panels.length);
}
// 축소된 modal PlayerPanel 찾기
const shrunkModalPlayerPanel = panels.find(
(panel) =>
panel.name === panel_names.PLAYER_PANEL &&
panel.panelInfo?.modal &&
panel.panelInfo?.shouldShrinkTo1px
);
if (DEBUG_MODE === true) {
dlog('[showModalVideo] *** 🔍 Shrunk modal PlayerPanel found:', !!shrunkModalPlayerPanel);
}
if (shrunkModalPlayerPanel) {
const panelInfo = shrunkModalPlayerPanel.panelInfo;
const shrinkInfo = panelInfo.playerState?.shrinkInfo;
if (DEBUG_MODE === true) {
dlog('[showModalVideo] *** 📋 ShrinkInfo available:', !!shrinkInfo);
}
dlog('[showModalVideo] *** 📋 Current panelInfo state:', {
shouldShrinkTo1px: panelInfo.shouldShrinkTo1px,
modal: panelInfo.modal,
modalContainerId: panelInfo.modalContainerId,
hasModalStyle: !!panelInfo.modalStyle,
});
const updatedPanelInfo = {
...panelInfo,
shouldShrinkTo1px: false, // 축소 플래그 해제
// 저장된 정보로 복구 (하지만 DOM 재계산은 허용)
...(shrinkInfo && {
modalContainerId: shrinkInfo.modalContainerId,
modalClassName: shrinkInfo.modalClassName,
modalStyle: shrinkInfo.modalStyle,
modalScale: shrinkInfo.modalScale,
}),
skipModalStyleRecalculation: false, // 위치 변경 시 DOM 기준으로 다시 계산하도록 허용
};
dlog(
'[showModalVideo] *** 🔄 Updated panelInfo - shouldShrinkTo1px:',
updatedPanelInfo.shouldShrinkTo1px
);
dlog('[showModalVideo] *** 📍 Restored modalStyle:', updatedPanelInfo.modalStyle);
dispatch(
updatePanel({
name: panel_names.PLAYER_PANEL,
panelInfo: updatedPanelInfo,
})
);
dlog('[showModalVideo] *** ✨ updatePanel dispatched');
} else {
dlog('[showModalVideo] *** ⚠️ No shrunk modal PlayerPanel found', {
panels: panels.map((p) => ({
name: p.name,
modal: p.panelInfo?.modal,
shouldShrinkTo1px: p.panelInfo?.shouldShrinkTo1px,
})),
});
}
dlog('[showModalVideo] *** ✅ END');
};
// 🔽 패널은 유지하고 비디오만 중지하는 함수들
/**
* 패널을 닫지 않고(popPanel 하지 않고) 비디오만 중지합니다.
* 모달 비디오의 재생을 중지하고 숨김 상태로 만듭니다.
*/
export const stopModalVideoWithoutClosingPanel = () => (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) {
dlog('[stopModalVideoWithoutClosingPanel] Stopping modal video playback');
// 타이머 정리
if (startVideoFocusTimer) {
clearTimeout(startVideoFocusTimer);
startVideoFocusTimer = null;
}
// 패널은 유지하되, 비디오 중지 상태로 업데이트
dispatch(
updatePanel({
name: panel_names.PLAYER_PANEL,
panelInfo: {
...modalPlayerPanel.panelInfo,
shouldStop: true, // 비디오 중지 플래그
isPaused: true, // 일시정지 상태
isHidden: true, // 화면에서 숨김
},
})
);
// Redux 상태도 중지로 업데이트
dispatch(setVideoStopped());
}
};
/**
* 패널을 닫지 않고 전체화면 비디오만 중지합니다.
*/
export const stopFullscreenVideoWithoutClosingPanel = () => (dispatch, getState) => {
const panels = getState().panels.panels;
// 전체화면 PlayerPanel 찾기
const fullscreenPlayerPanel = panels.find(
(panel) => panel.name === panel_names.PLAYER_PANEL && !panel.panelInfo?.modal
);
if (fullscreenPlayerPanel) {
dlog('[stopFullscreenVideoWithoutClosingPanel] Stopping fullscreen video playback');
// 타이머 정리
if (startVideoFocusTimer) {
clearTimeout(startVideoFocusTimer);
startVideoFocusTimer = null;
}
// 패널은 유지하되, 비디오 중지 상태로 업데이트
dispatch(
updatePanel({
name: panel_names.PLAYER_PANEL,
panelInfo: {
...fullscreenPlayerPanel.panelInfo,
shouldStop: true, // 비디오 중지 플래그
isPaused: true,
isHidden: true,
},
})
);
// Redux 상태도 중지로 업데이트
dispatch(setVideoStopped());
}
};
/**
* 모든 비디오(모달+전체화면)를 패널 닫지 않고 중지합니다.
*/
export const stopAllVideosWithoutClosingPanel = () => (dispatch, getState) => {
const panels = getState().panels.panels;
// 모든 PlayerPanel 찾기
const playerPanels = panels.filter((panel) => panel.name === panel_names.PLAYER_PANEL);
if (playerPanels.length > 0) {
dlog('[stopAllVideosWithoutClosingPanel] Stopping all video playback');
// 타이머 정리
if (startVideoFocusTimer) {
clearTimeout(startVideoFocusTimer);
startVideoFocusTimer = null;
}
// 모든 PlayerPanel을 중지 상태로 업데이트
playerPanels.forEach((playerPanel) => {
dispatch(
updatePanel({
name: panel_names.PLAYER_PANEL,
panelInfo: {
...playerPanel.panelInfo,
shouldStop: true,
isPaused: true,
isHidden: true,
},
})
);
});
// Redux 상태도 중지로 업데이트
dispatch(setVideoStopped());
}
};
// 채팅 로그 가져오기 IF-LGSP-371
export const getChatLog =
({ patnrId, showId }) =>
(dispatch, getState) => {
const onSuccess = (response) => {
dlog('getChatLog onSuccess', response.data);
dispatch({
type: types.GET_CHAT_LOG,
payload: response.data.data,
});
};
const onFail = (error) => {
derror('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) => {
dlog('getSubTitle onSuccess', response.data);
dispatch({
type: types.GET_SUBTITLE,
payload: { url: showSubtitleUrl, data: response.data.data },
});
};
const onFail = (error) => {
derror('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 {
dlog("playActions getSubTitle no Nothing it's exist", showSubtitleUrl);
}
};
export const CLEAR_PLAYER_INFO = () => ({
type: types.CLEAR_PLAYER_INFO,
});
// 특정 자막 Blob URL을 해제하는 액션 생성자
export const clearSubtitleBlob = (subtitleUrl) => (dispatch, getState) => {
const currentBlobs = getState().play.subTitleBlobs;
const blobUrl = currentBlobs[subtitleUrl];
// Blob URL 해제
if (blobUrl && blobUrl.startsWith('blob:')) {
try {
URL.revokeObjectURL(blobUrl);
dlog('[clearSubtitleBlob] Revoked Blob URL:', subtitleUrl);
} catch (error) {
derror('[clearSubtitleBlob] Failed to revoke Blob URL:', error);
}
}
// Redux 상태에서 제거
dispatch({
type: types.CLEAR_SUBTITLE_BLOB,
payload: { subtitleUrl }
});
};
/**
* 비디오 재생 상태를 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) => (dispatch, getState) => {
const currentState = getState().play.videoPlayState;
// 상태 변화 감지
const hasChanges = Object.keys(playState).some((key) => {
return currentState[key] !== playState[key];
});
if (hasChanges) {
dlog('🔄 [PlayerPanel] updateVideoPlayState action created', {
...playState,
timestamp: new Date().toISOString(),
caller: new Error().stack?.split('\n')[2]?.trim() || 'unknown',
});
}
dispatch({
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) => {
console.log('[Detail-BG] 🎬 goToFullScreen - Setting PlayerPanel to fullscreen mode', {
targetModal: false,
action: 'updatePanel',
timestamp: Date.now(),
});
// 공유 PlayerPanel의 'modal' 상태를 false로 변경하여 전체화면으로 전환
dispatch(
updatePanel({
name: panel_names.PLAYER_PANEL,
panelInfo: {
modal: false,
isHidden: false, // 혹시 숨겨져 있었다면 보이도록
},
})
);
console.log('[Detail-BG] ✅ goToFullScreen - PlayerPanel modal set to false (fullscreen)', {
timestamp: Date.now(),
});
};
/**
* 영구재생 비디오를 일시정지 상태로 만듭니다. (내부 사용)
*/
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());
}
};
/* 🔽 [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(),
},
});
/**
* 배너 비디오를 시작합니다.
* @param {Object} videoInfo - 비디오 정보
* @param {string} videoInfo.bannerId - 배너 ID
* @param {string} videoInfo.videoId - 비디오 ID
* @param {string} videoInfo.showUrl - 비디오 URL
* @param {boolean} videoInfo.modal - 모달 여부
* @param {string} videoInfo.modalContainerId - 모달 컨테이너 ID
* @param {string} videoInfo.modalClassName - 모달 클래스 이름
*/
export const startBannerVideo = (videoInfo) => (dispatch, getState) => {
dlog('[startBannerVideo] ✅ START - videoInfo:', videoInfo);
const {
bannerId,
videoId,
showUrl,
modal = true,
modalContainerId,
modalClassName,
...rest
} = videoInfo;
console.log('[Detail-BG] 🎥 startBannerVideo - Starting banner video', {
modalStatus: modal,
bannerId,
displayMode: modal ? 'VISIBLE (modal=true)' : 'FULLSCREEN (modal=false)',
videoId,
timestamp: Date.now(),
});
// 비디오 식별자 생성
const videoIdentifier = videoId || showUrl || bannerId;
if (videoIdentifier) {
const displayMode = modal ? DISPLAY_STATUS.VISIBLE : DISPLAY_STATUS.FULLSCREEN;
dlog(
'[startBannerVideo] 📌 Setting playback loading - identifier:',
videoIdentifier,
', displayMode:',
displayMode
);
dispatch(setPlaybackLoading(videoIdentifier, displayMode));
}
const panels = getState().panels.panels;
const existingPlayerPanel = panels.find((p) => p.name === panel_names.PLAYER_PANEL);
// 기존 PlayerPanel이 있으면 초기화
if (existingPlayerPanel) {
dlog('[startBannerVideo] 🔄 Resetting existing PLAYER_PANEL before start');
console.log('[Detail-BG] 🔄 startBannerVideo - Clearing existing PlayerPanel', {
existingModalStatus: existingPlayerPanel.panelInfo?.modal,
timestamp: Date.now(),
});
clearAllVideoTimers();
dispatch(popPanel(panel_names.PLAYER_PANEL));
}
// 새로운 PlayerPanel push
console.log('[Detail-BG] startBannerVideo - Pushing new PlayerPanel with modal status', {
modal,
modalContainerId,
timestamp: Date.now(),
});
dispatch(
pushPanel(
{
name: panel_names.PLAYER_PANEL,
panelInfo: {
modal,
modalContainerId,
modalClassName,
playerState: {
currentBannerId: bannerId,
},
videoId,
showUrl,
bannerId,
...rest,
},
},
true
)
);
console.log('[Detail-BG] ✅ startBannerVideo - PlayerPanel pushed with modal=' + modal, {
timestamp: Date.now(),
});
dlog('[startBannerVideo] ✨ Panel action dispatched');
};
/**
* 비디오를 중지하고 화면에서 숨깁니다.
* 패널을 닫지 않고 비디오 재생만 중지합니다.
*/
export const stopAndHideVideo = () => (dispatch, getState) => {
const panels = getState().panels.panels;
// 모든 PlayerPanel 찾기
const playerPanels = panels.filter((panel) => panel.name === panel_names.PLAYER_PANEL);
if (playerPanels.length > 0) {
dlog('[stopAndHideVideo] Stopping all video playback and hiding');
// 타이머 정리
if (startVideoFocusTimer) {
clearTimeout(startVideoFocusTimer);
startVideoFocusTimer = null;
}
// 모든 PlayerPanel을 중지 및 숨김 상태로 업데이트
playerPanels.forEach((playerPanel) => {
dispatch(
updatePanel({
name: panel_names.PLAYER_PANEL,
panelInfo: {
...playerPanel.panelInfo,
shouldStop: true,
isPaused: true,
isHidden: true,
},
})
);
});
// Redux 상태도 중지로 업데이트
dispatch(setVideoStopped());
}
};