[251013] feat:Video관련 action 추가

🕐 커밋 시간: 2025. 10. 13. 12:37:46

📊 변경 통계:
  • 총 파일: 7개
  • 추가: +95줄
  • 삭제: -168줄

📁 추가된 파일:
  + com.twin.app.shoptime/[251013]_RollingUnit_리렌더링_분석_및_해결방법.md

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/actions/actionTypes.js
  ~ com.twin.app.shoptime/src/actions/playActions.js
  ~ com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.js
  ~ com.twin.app.shoptime/src/reducers/playReducer.js
  ~ com.twin.app.shoptime/src/views/HomePanel/HomeBanner/HomeBanner.jsx
  ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx

🔧 함수 변경 내용:
  📄 com.twin.app.shoptime/src/actions/playActions.js (javascript):
    🔄 Modified: pauseModalVideo()
  📄 com.twin.app.shoptime/src/views/HomePanel/HomeBanner/HomeBanner.jsx (javascript):
    🔄 Modified: SpotlightContainerDecorator()

🔧 주요 변경 내용:
  • 타입 시스템 안정성 강화
  • 핵심 비즈니스 로직 개선
  • UI 컴포넌트 아키텍처 개선
  • 개발 문서 및 가이드 개선

Performance: 코드 최적화로 성능 개선 기대
This commit is contained in:
2025-10-13 12:37:48 +09:00
parent 68daf689c2
commit 599796696c
7 changed files with 716 additions and 376 deletions

View File

@@ -0,0 +1,416 @@
# [251013] RollingUnit 리렌더링 분석 및 해결방법
## 📋 문제 요약
- **증상**: HomeBanner의 RollingUnit 컴포넌트가 계속 리렌더링됨
- **특이사항**:
- RandomUnit은 정상 작동
- JustForYou 배너 관련 위치에서 특히 심함
- RollingUnit의 자동 순환(10초 롤링)이 제대로 작동하지 않음
- **원인**: OptionalTerms(선택약관) 관련 코드 추가 후 발생
---
## 🔍 근본 원인 분석
### 1. 불안정한 Props Chain (HomePanel → HomeBanner → RollingUnit)
**문제 흐름:**
```
HomePanel.jsx
↓ nowShelf 상태 변경
↓ doSendLogGNB 재생성 (의존성: nowShelf, panelInfo.nowShelf, pageSpotIds)
↓ handleItemFocus 재생성 (의존성: doSendLogGNB)
↓ handleItemFocus(el.shptmApphmDspyOptCd) 호출 → 매번 새 함수 생성
HomeBanner.jsx
↓ _handleItemFocus 재생성 (의존성: handleItemFocus)
↓ renderItem 재생성 (의존성: _handleItemFocus, _handleShelfFocus, bannerDataList)
RollingUnit.jsx
↓ 새로운 props 수신
↓ 리렌더링 발생!
```
**코드 위치:**
- `HomePanel.jsx` 라인 220-270
- `HomeBanner.jsx` 라인 81-85, 348-352, 497-542
---
### 2. OptionalTerms 상태와 조건부 렌더링 문제 ⚠️ **핵심 원인**
**문제 코드:** `HomeBanner.jsx` 라인 628-680
```javascript
const renderLayout = useCallback(() => {
switch (selectTemplate) {
case 'DSP00201': {
return (
<>
<ContainerBasic className={css.smallBox}>
{renderItem(0, true, true)}
{renderItem(1, true, true)}
</ContainerBasic>
{renderItem(2, false, false)}
{/* ⚠️ 문제 부분: introTermsAgree 상태에 의존 */}
{introTermsAgree === 'Y' ? (
<div className={css.imgBox}>
<JustForSwitchBanner
renderItem={renderItem}
handleShelfFocus={_handleShelfFocus}
handleItemFocus={_handleItemFocus}
isHorizontal={false}
spotlightId={'banner3'}
/>
</div>
) : (
renderItem(3, false, false)
)}
</>
);
}
}
return null;
}, [selectTemplate, renderItem, renderSimpleVideoContainer]);
// ❌ 문제: introTermsAgree가 의존성 배열에 없음!
```
**문제점:**
1. `introTermsAgree`가 JSX 내부에서 참조되지만 의존성 배열에 없음
2. `introTermsAgree` 변경 시 React가 최적화를 제대로 하지 못함
3. 클로저 문제로 인해 예상치 못한 리렌더링 발생
---
### 3. JustForSwitchBanner의 구조적 문제
**문제 코드:** `JustForYouBanner.jsx` 라인 154-230
```javascript
export default function JustForSwitchBanner({
renderItem, // ← HomeBanner에서 받은 renderItem
handleShelfFocus, // ← 계속 변하는 함수
handleItemFocus, // ← 계속 변하는 함수
isHorizontal,
spotlightId,
}) {
const [currentIndex, setCurrentIndex] = useState(0);
const onFocus = useCallback(() => {
if (handleItemFocus) {
handleItemFocus(); // ⚠️ 매번 새로운 함수 참조!
}
}, [handleItemFocus]); // ← 의존성: 계속 변경됨
return (
<>
<SpottableComponent onClick={handlePrev} />
{currentIndex === 0 ? (
<JustForYouBanner
onFocus={onFocus} // ← 계속 변하는 콜백!
/>
) : (
renderItem(3, false) // ⚠️ RollingUnit 렌더링!
)}
<SpottableComponent onClick={handleNext} />
</>
);
}
```
**문제의 연쇄 반응:**
```
handleItemFocus 변경
→ JustForSwitchBanner의 onFocus 재생성
→ JustForYouBanner 리렌더링
→ currentIndex === 1일 때 renderItem(3) 호출
→ RollingUnit 리렌더링!
```
---
### 4. RollingUnit 자동 롤링이 작동하지 않는 이유
**코드 위치:** `RollingUnit.jsx` 라인 463-487
```javascript
// 10초 롤링
useEffect(() => {
lastIndexRef.current = rollingDataLength - 1;
previousTimeRef.current = undefined; // ⚠️ 타이머 리셋!
if (rollingDataLength <= 1 || unitHasFocus) {
doRollingRef.current = false;
window.cancelAnimationFrame(requestRef.current);
return;
}
doRollingRef.current = true;
requestRef.current = window.requestAnimationFrame(animate);
return () => {
doRollingRef.current = false;
window.cancelAnimationFrame(requestRef.current);
};
}, [rollingDataLength, unitHasFocus]);
```
**문제:**
- RollingUnit이 계속 리렌더링되면 이 `useEffect`가 계속 실행됨
- `previousTimeRef.current = undefined`로 타이머가 계속 리셋됨
- `requestAnimationFrame`이 취소되고 다시 시작됨
- **결과: 10초 타이머가 초기화되어 자동 롤링이 작동하지 않음**
---
### 5. RandomUnit은 왜 괜찮은가?
**RandomUnit의 경우:**
```javascript
// HomeBanner.jsx - renderLayout (DSP00201)
<ContainerBasic className={css.smallBox}>
{renderItem(0, true, true)} // ← Rolling 또는 Random
{renderItem(1, true, true)} // ← 주로 Random (고정 위치)
</ContainerBasic>
```
**RandomUnit은:**
- ✅ 고정된 위치(index 0, 1)에서 렌더링
- ✅ 조건부 렌더링 없음
-`introTermsAgree` 상태와 무관
- ✅ JustForSwitchBanner를 통하지 않음
- ✅ 안정적인 props만 받음
---
## 💡 해결방법
### ✅ 해결방법 1: renderLayout 의존성 배열에 introTermsAgree 추가 (적용됨)
**파일:** `HomeBanner.jsx` 라인 514
**변경 전:**
```javascript
}, [selectTemplate, renderItem, renderSimpleVideoContainer]);
```
**변경 후:**
```javascript
}, [selectTemplate, renderItem, renderSimpleVideoContainer, introTermsAgree]);
```
**효과:**
- `introTermsAgree` 변경 시 `renderLayout`이 명시적으로 재생성됨
- React가 변경사항을 정확히 추적할 수 있음
- 클로저 문제 해결
---
### 🔧 추가 권장 해결방법
#### 해결방법 2: HomePanel의 handleItemFocus 최적화
**파일:** `HomePanel.jsx` 라인 265-270
**현재 문제:**
```javascript
const handleItemFocus = useCallback(
(containerId, location, title) => () => {
doSendLogGNB(containerId, location, title);
},
[doSendLogGNB]
);
// 렌더링 시 매번 새 함수 생성
<HomeBanner
handleItemFocus={handleItemFocus(el.shptmApphmDspyOptCd)}
/>
```
**해결방법:**
```javascript
// 1. 함수를 직접 전달하지 말고 파라미터를 전달
<HomeBanner
containerId={el.shptmApphmDspyOptCd}
location={el.expsOrd}
title={el.shptmApphmDspyOptNm}
onItemFocus={handleItemFocus} // 안정적인 함수 참조
/>
// 2. HomeBanner에서 필요할 때 호출
const _handleItemFocus = useCallback(() => {
if (onItemFocus) {
onItemFocus(containerId, location, title);
}
}, [onItemFocus, containerId, location, title]);
```
---
#### 해결방법 3: JustForSwitchBanner 최적화
**파일:** `JustForYouBanner.jsx` 라인 189-195
**현재 문제:**
```javascript
const onFocus = useCallback(() => {
if (handleItemFocus) {
handleItemFocus();
}
}, [handleItemFocus]); // ← 계속 변경됨
```
**해결방법 A: useRef 사용**
```javascript
const handleItemFocusRef = useRef(handleItemFocus);
useEffect(() => {
handleItemFocusRef.current = handleItemFocus;
}, [handleItemFocus]);
const onFocus = useCallback(() => {
if (handleItemFocusRef.current) {
handleItemFocusRef.current();
}
}, []); // ← 의존성 없음, 안정적
```
**해결방법 B: React.memo로 JustForYouBanner 감싸기**
```javascript
const JustForYouBanner = React.memo(function JustForYouBanner({
onClick,
spotlightId,
onFocus,
isHorizontal,
popupVisible,
activePopup,
}) {
// ... 기존 코드
}, (prevProps, nextProps) => {
// onFocus가 변경되어도 리렌더링하지 않음
return (
prevProps.spotlightId === nextProps.spotlightId &&
prevProps.isHorizontal === nextProps.isHorizontal &&
prevProps.popupVisible === nextProps.popupVisible &&
prevProps.activePopup === nextProps.activePopup
);
});
```
---
#### 해결방법 4: doSendLogGNB 의존성 최적화
**파일:** `HomePanel.jsx` 라인 220-263
**현재 문제:**
```javascript
const doSendLogGNB = useCallback(
(containerId, location = null, title = null) => {
// ... 로직
},
[pageSpotIds, nowShelf, panelInfo.nowShelf] // ← 자주 변경됨
);
```
**해결방법: useRef 사용**
```javascript
const nowShelfRef = useRef(nowShelf);
const panelInfoRef = useRef(panelInfo.nowShelf);
useEffect(() => {
nowShelfRef.current = nowShelf;
panelInfoRef.current = panelInfo.nowShelf;
}, [nowShelf, panelInfo.nowShelf]);
const doSendLogGNB = useCallback(
(containerId, location = null, title = null) => {
// nowShelfRef.current 사용
if (containerId !== nowShelfRef.current && location && title) {
// ... 로직
}
},
[pageSpotIds] // ← 의존성 감소
);
```
---
#### 해결방법 5: RollingUnit을 React.memo로 감싸기
**파일:** `RollingUnit.jsx` 마지막 줄
**현재:**
```javascript
export default function RollingUnit({ ... }) {
// ... 코드
}
```
**해결방법:**
```javascript
function RollingUnit({ ... }) {
// ... 기존 코드
}
export default React.memo(RollingUnit, (prevProps, nextProps) => {
// props가 실제로 변경되었을 때만 리렌더링
return (
prevProps.bannerData === nextProps.bannerData &&
prevProps.spotlightId === nextProps.spotlightId &&
prevProps.isHorizontal === nextProps.isHorizontal &&
prevProps.videoPlayerable === nextProps.videoPlayerable
// handleItemFocus, handleShelfFocus는 비교하지 않음
);
});
```
---
## 📊 우선순위별 적용 순서
### 1단계 (필수) ✅ 완료
- [x] `renderLayout` 의존성 배열에 `introTermsAgree` 추가
### 2단계 (권장)
- [ ] `doSendLogGNB` 의존성 최적화 (useRef 사용)
- [ ] `handleItemFocus` 구조 개선 (파라미터 전달 방식)
### 3단계 (선택)
- [ ] `JustForSwitchBanner` 최적화 (useRef 또는 React.memo)
- [ ] `RollingUnit`을 React.memo로 감싸기
---
## 🎯 예상 효과
### 1단계 적용 후:
-`introTermsAgree` 변경 시 명시적 리렌더링
- ✅ 클로저 문제 해결
- ⚠️ 여전히 props chain으로 인한 불필요한 리렌더링 가능
### 2-3단계 적용 후:
- ✅ 불필요한 리렌더링 대폭 감소
- ✅ RollingUnit 자동 롤링 정상 작동
- ✅ 성능 개선
- ✅ 안정적인 컴포넌트 동작
---
## 📝 테스트 체크리스트
- [ ] RollingUnit이 불필요하게 리렌더링되지 않는지 확인
- [ ] RollingUnit의 10초 자동 롤링이 정상 작동하는지 확인
- [ ] OptionalTerms 동의 전/후 배너 전환이 정상적인지 확인
- [ ] JustForYou 배너와 RollingUnit 간 전환이 부드러운지 확인
- [ ] RandomUnit이 여전히 정상 작동하는지 확인
- [ ] 포커스 이동 시 로그가 정상적으로 전송되는지 확인
---
## 📅 작성 정보
- **날짜**: 2025-10-13
- **분석 대상**: RollingUnit 리렌더링 문제
- **주요 원인**: OptionalTerms 상태 + JustForSwitchBanner 조건부 렌더링
- **적용된 해결방법**: renderLayout 의존성 배열에 introTermsAgree 추가

View File

@@ -2,218 +2,219 @@
export const types = { export const types = {
// panel actions // panel actions
PUSH_PANEL: "PUSH_PANEL", PUSH_PANEL: 'PUSH_PANEL',
POP_PANEL: "POP_PANEL", POP_PANEL: 'POP_PANEL',
UPDATE_PANEL: "UPDATE_PANEL", UPDATE_PANEL: 'UPDATE_PANEL',
RESET_PANELS: "RESET_PANELS", RESET_PANELS: 'RESET_PANELS',
// device actions // device actions
GET_AUTHENTICATION_CODE: "GET_AUTHENTICATION_CODE", GET_AUTHENTICATION_CODE: 'GET_AUTHENTICATION_CODE',
REGISTER_DEVICE: "REGISTER_DEVICE", REGISTER_DEVICE: 'REGISTER_DEVICE',
REGISTER_DEVICE_INFO: "REGISTER_DEVICE_INFO", REGISTER_DEVICE_INFO: 'REGISTER_DEVICE_INFO',
GET_DEVICE_INFO: "GET_DEVICE_INFO", GET_DEVICE_INFO: 'GET_DEVICE_INFO',
CLEAR_REGISTER_DEVICE_INFO: "CLEAR_REGISTER_DEVICE_INFO", CLEAR_REGISTER_DEVICE_INFO: 'CLEAR_REGISTER_DEVICE_INFO',
REGISTER_DEVICE_RESET: "REGISTER_DEVICE_RESET", REGISTER_DEVICE_RESET: 'REGISTER_DEVICE_RESET',
// common actions // common actions
GET_HTTP_HEADER: "GET_HTTP_HEADER", GET_HTTP_HEADER: 'GET_HTTP_HEADER',
CHANGE_APP_STATUS: "CHANGE_APP_STATUS", CHANGE_APP_STATUS: 'CHANGE_APP_STATUS',
SEND_BROADCAST: "SEND_BROADCAST", SEND_BROADCAST: 'SEND_BROADCAST',
CHANGE_LOCAL_SETTINGS: "CHANGE_LOCAL_SETTINGS", CHANGE_LOCAL_SETTINGS: 'CHANGE_LOCAL_SETTINGS',
GNB_OPENED: "GNB_OPENED", GNB_OPENED: 'GNB_OPENED',
SET_SHOW_POPUP: "SET_SHOW_POPUP", SET_SHOW_POPUP: 'SET_SHOW_POPUP',
SET_SHOW_SECONDARY_POPUP: "SET_SHOW_SECONDARY_POPUP", SET_SHOW_SECONDARY_POPUP: 'SET_SHOW_SECONDARY_POPUP',
SET_HIDE_POPUP: "SET_HIDE_POPUP", SET_HIDE_POPUP: 'SET_HIDE_POPUP',
SET_HIDE_SECONDARY_POPUP: "SET_HIDE_SECONDARY_POPUP", SET_HIDE_SECONDARY_POPUP: 'SET_HIDE_SECONDARY_POPUP',
SHOW_OPTIONAL_TERMS_CONFIRM_POPUP: "SHOW_OPTIONAL_TERMS_CONFIRM_POPUP", SHOW_OPTIONAL_TERMS_CONFIRM_POPUP: 'SHOW_OPTIONAL_TERMS_CONFIRM_POPUP',
HIDE_OPTIONAL_TERMS_CONFIRM_POPUP: "HIDE_OPTIONAL_TERMS_CONFIRM_POPUP", HIDE_OPTIONAL_TERMS_CONFIRM_POPUP: 'HIDE_OPTIONAL_TERMS_CONFIRM_POPUP',
TOGGLE_OPTIONAL_TERMS_CONFIRM: "TOGGLE_OPTIONAL_TERMS_CONFIRM", TOGGLE_OPTIONAL_TERMS_CONFIRM: 'TOGGLE_OPTIONAL_TERMS_CONFIRM',
// 선택약관 팝업 상태 관리 // 선택약관 팝업 상태 관리
SET_OPTIONAL_TERMS_POPUP_SHOWN: "SET_OPTIONAL_TERMS_POPUP_SHOWN", SET_OPTIONAL_TERMS_POPUP_SHOWN: 'SET_OPTIONAL_TERMS_POPUP_SHOWN',
SET_OPTIONAL_TERMS_USER_DECISION: "SET_OPTIONAL_TERMS_USER_DECISION", SET_OPTIONAL_TERMS_USER_DECISION: 'SET_OPTIONAL_TERMS_USER_DECISION',
RESET_OPTIONAL_TERMS_SESSION: "RESET_OPTIONAL_TERMS_SESSION", RESET_OPTIONAL_TERMS_SESSION: 'RESET_OPTIONAL_TERMS_SESSION',
// 선택약관 직접 상태 업데이트 (API 호출 없이) // 선택약관 직접 상태 업데이트 (API 호출 없이)
UPDATE_OPTIONAL_TERMS_AGREE_DIRECT: "UPDATE_OPTIONAL_TERMS_AGREE_DIRECT", UPDATE_OPTIONAL_TERMS_AGREE_DIRECT: 'UPDATE_OPTIONAL_TERMS_AGREE_DIRECT',
UPDATE_TERMS_AGREEMENT_STATUS_DIRECT: "UPDATE_TERMS_AGREEMENT_STATUS_DIRECT", UPDATE_TERMS_AGREEMENT_STATUS_DIRECT: 'UPDATE_TERMS_AGREEMENT_STATUS_DIRECT',
SET_EXIT_APP: "SET_EXIT_APP", SET_EXIT_APP: 'SET_EXIT_APP',
GET_LOGIN_USER_DATA: "GET_LOGIN_USER_DATA", GET_LOGIN_USER_DATA: 'GET_LOGIN_USER_DATA',
GET_TERMS_AGREE_YN: "GET_TERMS_AGREE_YN", GET_TERMS_AGREE_YN: 'GET_TERMS_AGREE_YN',
LAUNCH_MEMBERSHIP_APP: "LAUNCH_MEMBERSHIP_APP", LAUNCH_MEMBERSHIP_APP: 'LAUNCH_MEMBERSHIP_APP',
SET_FOCUS: "SET_FOCUS", SET_FOCUS: 'SET_FOCUS',
SET_GNB_MENU: "SET_GNB_MENU", SET_GNB_MENU: 'SET_GNB_MENU',
SET_SYSTEM_NOTICE: "SET_SYSTEM_NOTICE", SET_SYSTEM_NOTICE: 'SET_SYSTEM_NOTICE',
SET_SYSTEM_TERMINATION: "SET_SYSTEM_TERMINATION", SET_SYSTEM_TERMINATION: 'SET_SYSTEM_TERMINATION',
SET_DEEP_LINK: "SET_DEEP_LINK", SET_DEEP_LINK: 'SET_DEEP_LINK',
SET_SECOND_LAYER_INFO: "SET_SECOND_LAYER_INFO", SET_SECOND_LAYER_INFO: 'SET_SECOND_LAYER_INFO',
SET_ERROR_MESSAGE: "SET_ERROR_MESSAGE", SET_ERROR_MESSAGE: 'SET_ERROR_MESSAGE',
CLEAR_ERROR_MESSAGE: "CLEAR_ERROR_MESSAGE", CLEAR_ERROR_MESSAGE: 'CLEAR_ERROR_MESSAGE',
GET_DEVICE_MACADDRESS: "GET_DEVICE_MACADDRESS", GET_DEVICE_MACADDRESS: 'GET_DEVICE_MACADDRESS',
SET_DEVICE_REGISTERED: "SET_DEVICE_REGISTERED", SET_DEVICE_REGISTERED: 'SET_DEVICE_REGISTERED',
// billing actions // billing actions
GET_MY_INFO_BILLING_SEARCH: "GET_MY_INFO_BILLING_SEARCH", GET_MY_INFO_BILLING_SEARCH: 'GET_MY_INFO_BILLING_SEARCH',
// card actions // card actions
GET_MY_INFO_CARD_SEARCH: "GET_MY_INFO_CARD_SEARCH", GET_MY_INFO_CARD_SEARCH: 'GET_MY_INFO_CARD_SEARCH',
// shipping actions // shipping actions
GET_MY_INFO_SHIPPING_SEARCH: "GET_MY_INFO_SHIPPING_SEARCH", GET_MY_INFO_SHIPPING_SEARCH: 'GET_MY_INFO_SHIPPING_SEARCH',
// cart actions // cart actions
GET_MY_INFO_CART_SEARCH: "GET_MY_INFO_CART_SEARCH", GET_MY_INFO_CART_SEARCH: 'GET_MY_INFO_CART_SEARCH',
ADD_TO_CART: "ADD_TO_CART", ADD_TO_CART: 'ADD_TO_CART',
REMOVE_FROM_CART: "REMOVE_FROM_CART", REMOVE_FROM_CART: 'REMOVE_FROM_CART',
UPDATE_CART_ITEM: "UPDATE_CART_ITEM", UPDATE_CART_ITEM: 'UPDATE_CART_ITEM',
CLEAR_CART: "CLEAR_CART", CLEAR_CART: 'CLEAR_CART',
// appData actions // appData actions
ADD_MAIN_INDEX: "ADD_MAIN_INDEX", ADD_MAIN_INDEX: 'ADD_MAIN_INDEX',
SEND_SMS: "SEND_SMS", SEND_SMS: 'SEND_SMS',
CLEAR_SMS: "CLEAR_SMS", CLEAR_SMS: 'CLEAR_SMS',
// home actions // home actions
GET_HOME_TERMS: "GET_HOME_TERMS", GET_HOME_TERMS: 'GET_HOME_TERMS',
SET_TERMS_ID_MAP: "SET_TERMS_ID_MAP", SET_TERMS_ID_MAP: 'SET_TERMS_ID_MAP',
SET_OPTIONAL_TERMS_AVAILABILITY: "SET_OPTIONAL_TERMS_AVAILABILITY", SET_OPTIONAL_TERMS_AVAILABILITY: 'SET_OPTIONAL_TERMS_AVAILABILITY',
GET_HOME_MENU: "GET_HOME_MENU", GET_HOME_MENU: 'GET_HOME_MENU',
GET_HOME_LAYOUT: "GET_HOME_LAYOUT", GET_HOME_LAYOUT: 'GET_HOME_LAYOUT',
GET_HOME_MAIN_CONTENTS: "GET_HOME_MAIN_CONTENTS", GET_HOME_MAIN_CONTENTS: 'GET_HOME_MAIN_CONTENTS',
GET_HOME_FULL_VIDEO_INFO: "GET_HOME_FULL_VIDEO_INFO", GET_HOME_FULL_VIDEO_INFO: 'GET_HOME_FULL_VIDEO_INFO',
GET_THEME_CURATION_INFO: "GET_THEME_CURATION_INFO", GET_THEME_CURATION_INFO: 'GET_THEME_CURATION_INFO',
GET_THEME_CURATION_DETAIL_INFO: "GET_THEME_CURATION_DETAIL_INFO", GET_THEME_CURATION_DETAIL_INFO: 'GET_THEME_CURATION_DETAIL_INFO',
GET_THEME_HOTEL_DETAIL_INFO: "GET_THEME_HOTEL_DETAIL_INFO", GET_THEME_HOTEL_DETAIL_INFO: 'GET_THEME_HOTEL_DETAIL_INFO',
GET_THEME_MENU_SHELF_INFO: "GET_THEME_MENU_SHELF_INFO", GET_THEME_MENU_SHELF_INFO: 'GET_THEME_MENU_SHELF_INFO',
CLEAR_THEME_MENU_SHELF_INFO: "CLEAR_THEME_MENU_SHELF_INFO", CLEAR_THEME_MENU_SHELF_INFO: 'CLEAR_THEME_MENU_SHELF_INFO',
CLEAR_THEME_DETAIL: "CLEAR_THEME_DETAIL", CLEAR_THEME_DETAIL: 'CLEAR_THEME_DETAIL',
CHECK_ENTER_THROUGH_GNB: "CHECK_ENTER_THROUGH_GNB", CHECK_ENTER_THROUGH_GNB: 'CHECK_ENTER_THROUGH_GNB',
SET_DEFAULT_FOCUS: "SET_DEFAULT_FOCUS", SET_DEFAULT_FOCUS: 'SET_DEFAULT_FOCUS',
SET_BANNER_INDEX: "SET_BANNER_INDEX", SET_BANNER_INDEX: 'SET_BANNER_INDEX',
RESET_HOME_INFO: "RESET_HOME_INFO", RESET_HOME_INFO: 'RESET_HOME_INFO',
UPDATE_HOME_INFO: "UPDATE_HOME_INFO", UPDATE_HOME_INFO: 'UPDATE_HOME_INFO',
// brand actions // brand actions
GET_BRAND_LIST: "GET_BRAND_LIST", GET_BRAND_LIST: 'GET_BRAND_LIST',
GET_BRAND_LAYOUT_INFO: "GET_BRAND_LAYOUT_INFO", GET_BRAND_LAYOUT_INFO: 'GET_BRAND_LAYOUT_INFO',
GET_BRAND_LIVE_CHANNEL_INFO: "GET_BRAND_LIVE_CHANNEL_INFO", GET_BRAND_LIVE_CHANNEL_INFO: 'GET_BRAND_LIVE_CHANNEL_INFO',
GET_BRAND_CHAN_INFO: "GET_BRAND_CHAN_INFO", GET_BRAND_CHAN_INFO: 'GET_BRAND_CHAN_INFO',
GET_BRAND_TSV_INFO: "GET_BRAND_TSV_INFO", GET_BRAND_TSV_INFO: 'GET_BRAND_TSV_INFO',
GET_BRAND_RECOMMENDED_SHOW_INFO: "GET_BRAND_RECOMMENDED_SHOW_INFO", GET_BRAND_RECOMMENDED_SHOW_INFO: 'GET_BRAND_RECOMMENDED_SHOW_INFO',
GET_BRAND_SERIES_INFO: "GET_BRAND_SERIES_INFO", GET_BRAND_SERIES_INFO: 'GET_BRAND_SERIES_INFO',
GET_BRAND_CATEGORY_INFO: "GET_BRAND_CATEGORY_INFO", GET_BRAND_CATEGORY_INFO: 'GET_BRAND_CATEGORY_INFO',
GET_BRAND_CATEGORY_PRODUCT_INFO: "GET_BRAND_CATEGORY_PRODUCT_INFO", GET_BRAND_CATEGORY_PRODUCT_INFO: 'GET_BRAND_CATEGORY_PRODUCT_INFO',
GET_BRAND_BEST_SELLER: "GET_BRAND_BEST_SELLER", GET_BRAND_BEST_SELLER: 'GET_BRAND_BEST_SELLER',
GET_BRAND_CREATORS_INFO: "GET_BRAND_CREATORS_INFO", GET_BRAND_CREATORS_INFO: 'GET_BRAND_CREATORS_INFO',
GET_BRAND_SHOWROOM: "GET_BRAND_SHOWROOM", GET_BRAND_SHOWROOM: 'GET_BRAND_SHOWROOM',
GET_BRAND_RECENTLY_AIRED: "GET_BRAND_RECENTLY_AIRED", GET_BRAND_RECENTLY_AIRED: 'GET_BRAND_RECENTLY_AIRED',
SET_BRAND_LIVE_CHANNEL_UPCOMING: "SET_BRAND_LIVE_CHANNEL_UPCOMING", SET_BRAND_LIVE_CHANNEL_UPCOMING: 'SET_BRAND_LIVE_CHANNEL_UPCOMING',
SET_BRAND_CHAN_INFO: "SET_BRAND_CHAN_INFO", SET_BRAND_CHAN_INFO: 'SET_BRAND_CHAN_INFO',
RESET_BRAND_STATE: "RESET_BRAND_STATE", RESET_BRAND_STATE: 'RESET_BRAND_STATE',
RESET_BRAND_STATE_EXCEPT_BRAND_INFO: "RESET_BRAND_STATE_EXCEPT_BRAND_INFO", RESET_BRAND_STATE_EXCEPT_BRAND_INFO: 'RESET_BRAND_STATE_EXCEPT_BRAND_INFO',
RESET_BRAND_LAYOUT_INFO: "RESET_BRAND_LAYOUT_INFO", RESET_BRAND_LAYOUT_INFO: 'RESET_BRAND_LAYOUT_INFO',
// main actions // main actions
GET_SUB_CATEGORY: "GET_SUB_CATEGORY", GET_SUB_CATEGORY: 'GET_SUB_CATEGORY',
APPEND_SUB_CATEGORY: "APPEND_SUB_CATEGORY", APPEND_SUB_CATEGORY: 'APPEND_SUB_CATEGORY',
GET_TOP_20_SHOW: "GET_TOP_20_SHOW", GET_TOP_20_SHOW: 'GET_TOP_20_SHOW',
GET_PRODUCT_DETAIL: "GET_PRODUCT_DETAIL", GET_PRODUCT_DETAIL: 'GET_PRODUCT_DETAIL',
GET_YOUMAYLIKE: "GET_YOUMAYLIKE", GET_YOUMAYLIKE: 'GET_YOUMAYLIKE',
SET_MAIN_LIKE_CATEGORY: "SET_MAIN_LIKE_CATEGORY", SET_MAIN_LIKE_CATEGORY: 'SET_MAIN_LIKE_CATEGORY',
SET_MAIN_LIVE_UPCOMING_ALARM: "SET_MAIN_LIVE_UPCOMING_ALARM", SET_MAIN_LIVE_UPCOMING_ALARM: 'SET_MAIN_LIVE_UPCOMING_ALARM',
GET_MAIN_LIVE_SHOW_NOW_PRODUCT: "GET_MAIN_LIVE_SHOW_NOW_PRODUCT", GET_MAIN_LIVE_SHOW_NOW_PRODUCT: 'GET_MAIN_LIVE_SHOW_NOW_PRODUCT',
GET_MAIN_LIVE_SHOW: "GET_MAIN_LIVE_SHOW", GET_MAIN_LIVE_SHOW: 'GET_MAIN_LIVE_SHOW',
GET_MAIN_CATEGORY_SHOW_DETAIL: "GET_MAIN_CATEGORY_SHOW_DETAIL", GET_MAIN_CATEGORY_SHOW_DETAIL: 'GET_MAIN_CATEGORY_SHOW_DETAIL',
CLEAR_PRODUCT_DETAIL: "CLEAR_PRODUCT_DETAIL", CLEAR_PRODUCT_DETAIL: 'CLEAR_PRODUCT_DETAIL',
CLEAR_SUB_CATEGORY: "CLEAR_SUB_CATEGORY", CLEAR_SUB_CATEGORY: 'CLEAR_SUB_CATEGORY',
CLEAR_SHOPNOW_INFO: "CLEAR_SHOPNOW_INFO", CLEAR_SHOPNOW_INFO: 'CLEAR_SHOPNOW_INFO',
// myPage actions // myPage actions
GET_MY_RECOMMANDED_KEYWORD: "GET_MY_RECOMMANDED_KEYWORD", GET_MY_RECOMMANDED_KEYWORD: 'GET_MY_RECOMMANDED_KEYWORD',
GET_MY_FAQ_INFO: "GET_MY_FAQ_INFO", GET_MY_FAQ_INFO: 'GET_MY_FAQ_INFO',
GET_NOTICE: "GET_NOTICE", GET_NOTICE: 'GET_NOTICE',
GET_MY_CUSTOMERS: "GET_MY_CUSTOMERS", GET_MY_CUSTOMERS: 'GET_MY_CUSTOMERS',
GET_MY_FAVORITE: "GET_MY_FAVORITE", GET_MY_FAVORITE: 'GET_MY_FAVORITE',
DELETE_MY_FAVORITE: "DELETE_MY_FAVORITE", DELETE_MY_FAVORITE: 'DELETE_MY_FAVORITE',
GET_MY_FAVORITE_FLAG: "GET_MY_FAVORITE_FLAG", GET_MY_FAVORITE_FLAG: 'GET_MY_FAVORITE_FLAG',
SET_MY_TERMS_WITHDRAW: "SET_MY_TERMS_WITHDRAW", SET_MY_TERMS_WITHDRAW: 'SET_MY_TERMS_WITHDRAW',
GET_MY_UPCOMING_CHANGE_INFO: "GET_MY_UPCOMING_CHANGE_INFO", GET_MY_UPCOMING_CHANGE_INFO: 'GET_MY_UPCOMING_CHANGE_INFO',
GET_MY_UPCOMING_ALERT_SHOW: "GET_MY_UPCOMING_ALERT_SHOW", GET_MY_UPCOMING_ALERT_SHOW: 'GET_MY_UPCOMING_ALERT_SHOW',
DELETE_MY_UPCOMING_ALERT_SHOW: "DELETE_MY_UPCOMING_ALERT_SHOW", DELETE_MY_UPCOMING_ALERT_SHOW: 'DELETE_MY_UPCOMING_ALERT_SHOW',
GET_MY_UPCOMING_ALERT_SHOW_KEYS: "GET_MY_UPCOMING_ALERT_SHOW_KEYS", GET_MY_UPCOMING_ALERT_SHOW_KEYS: 'GET_MY_UPCOMING_ALERT_SHOW_KEYS',
SET_MY_UPCOMING_USE_ALERT: "SET_MY_UPCOMING_USE_ALERT", SET_MY_UPCOMING_USE_ALERT: 'SET_MY_UPCOMING_USE_ALERT',
GET_UPCOMING_ALERT_SHOW_CHANGE_INFO: "GET_UPCOMING_ALERT_SHOW_CHANGE_INFO", GET_UPCOMING_ALERT_SHOW_CHANGE_INFO: 'GET_UPCOMING_ALERT_SHOW_CHANGE_INFO',
GET_MY_RECENTLY_VIEWED_INFO: "GET_MY_RECENTLY_VIEWED_INFO", GET_MY_RECENTLY_VIEWED_INFO: 'GET_MY_RECENTLY_VIEWED_INFO',
CLEAR_RECENTLY_VIEWED_INFO: "CLEAR_RECENTLY_VIEWED_INFO", CLEAR_RECENTLY_VIEWED_INFO: 'CLEAR_RECENTLY_VIEWED_INFO',
CLEAR_FAVORITES: "CLEAR_FAVORITES", CLEAR_FAVORITES: 'CLEAR_FAVORITES',
SET_MYPAGE_TERMS_AGREE: "SET_MYPAGE_TERMS_AGREE", SET_MYPAGE_TERMS_AGREE: 'SET_MYPAGE_TERMS_AGREE',
SET_MYPAGE_TERMS_AGREE_SUCCESS: "SET_MYPAGE_TERMS_AGREE_SUCCESS", SET_MYPAGE_TERMS_AGREE_SUCCESS: 'SET_MYPAGE_TERMS_AGREE_SUCCESS',
SET_MYPAGE_TERMS_AGREE_FAIL: "SET_MYPAGE_TERMS_AGREE_FAIL", SET_MYPAGE_TERMS_AGREE_FAIL: 'SET_MYPAGE_TERMS_AGREE_FAIL',
// onSale actions // onSale actions
GET_HOME_ON_SALE_INFO: "GET_HOME_ON_SALE_INFO", GET_HOME_ON_SALE_INFO: 'GET_HOME_ON_SALE_INFO',
GET_ON_SALE_INFO: "GET_ON_SALE_INFO", GET_ON_SALE_INFO: 'GET_ON_SALE_INFO',
COPY_CATEGORY_INFO: "COPY_CATEGORY_INFO", COPY_CATEGORY_INFO: 'COPY_CATEGORY_INFO',
RESET_ON_SALE_STATE: "RESET_ON_SALE_STATE", RESET_ON_SALE_STATE: 'RESET_ON_SALE_STATE',
// product actions // product actions
GET_BEST_SELLER: "GET_BEST_SELLER", GET_BEST_SELLER: 'GET_BEST_SELLER',
GET_PRODUCT_GROUP: "GET_PRODUCT_GROUP", GET_PRODUCT_GROUP: 'GET_PRODUCT_GROUP',
GET_PRODUCT_OPTION: "GET_PRODUCT_OPTION", GET_PRODUCT_OPTION: 'GET_PRODUCT_OPTION',
GET_PRODUCT_IMAGE_LENGTH: "GET_PRODUCT_IMAGE_LENGTH", GET_PRODUCT_IMAGE_LENGTH: 'GET_PRODUCT_IMAGE_LENGTH',
GET_VIDEO_INDECATOR_FOCUS: "GET_VIDEO_INDECATOR_FOCUS", GET_VIDEO_INDECATOR_FOCUS: 'GET_VIDEO_INDECATOR_FOCUS',
GET_PRODUCT_OPTION_ID: "GET_PRODUCT_OPTION_ID", GET_PRODUCT_OPTION_ID: 'GET_PRODUCT_OPTION_ID',
CLEAR_PRODUCT_OPTIONS: "CLEAR_PRODUCT_OPTIONS", CLEAR_PRODUCT_OPTIONS: 'CLEAR_PRODUCT_OPTIONS',
GET_USER_REVIEW: "GET_USER_REVIEW", GET_USER_REVIEW: 'GET_USER_REVIEW',
TOGGLE_SHOW_ALL_REVIEWS: "TOGGLE_SHOW_ALL_REVIEWS", TOGGLE_SHOW_ALL_REVIEWS: 'TOGGLE_SHOW_ALL_REVIEWS',
RESET_SHOW_ALL_REVIEWS: "RESET_SHOW_ALL_REVIEWS", RESET_SHOW_ALL_REVIEWS: 'RESET_SHOW_ALL_REVIEWS',
// search actions // search actions
GET_SEARCH: "GET_SEARCH", GET_SEARCH: 'GET_SEARCH',
RESET_SEARCH: "RESET_SEARCH", RESET_SEARCH: 'RESET_SEARCH',
GET_SEARCH_PROCESSED: "GET_SEARCH_PROCESSED", GET_SEARCH_PROCESSED: 'GET_SEARCH_PROCESSED',
SET_SEARCH_INIT_PERFORMED: "SET_SEARCH_INIT_PERFORMED", SET_SEARCH_INIT_PERFORMED: 'SET_SEARCH_INIT_PERFORMED',
UPDATE_SEARCH_TIMESTAMP: "UPDATE_SEARCH_TIMESTAMP", UPDATE_SEARCH_TIMESTAMP: 'UPDATE_SEARCH_TIMESTAMP',
// event actions // event actions
GET_WELCOME_EVENT_INFO: "GET_WELCOME_EVENT_INFO", GET_WELCOME_EVENT_INFO: 'GET_WELCOME_EVENT_INFO',
GET_EVENT_ISSUED_STATUS: "GET_EVENT_ISSUED_STATUS", GET_EVENT_ISSUED_STATUS: 'GET_EVENT_ISSUED_STATUS',
SET_EVENT_ISSUE_REQ: "SET_EVENT_ISSUE_REQ", SET_EVENT_ISSUE_REQ: 'SET_EVENT_ISSUE_REQ',
SET_EVENT_POP_CLICK_INFO: "SET_EVENT_POP_CLICK_INFO", SET_EVENT_POP_CLICK_INFO: 'SET_EVENT_POP_CLICK_INFO',
CLEAR_EVENT_INFO: "CLEAR_EVENT_INFO", CLEAR_EVENT_INFO: 'CLEAR_EVENT_INFO',
SKIP_CLICK_INFO: "SKIP_CLICK_INFO", SKIP_CLICK_INFO: 'SKIP_CLICK_INFO',
CLEAR_CURATION_COUPON: "CLEAR_CURATION_COUPON", CLEAR_CURATION_COUPON: 'CLEAR_CURATION_COUPON',
CLEAR_EVENT_ISSUED_STATUS_SUCCEESS: "CLEAR_EVENT_ISSUED_STATUS_SUCCEESS", CLEAR_EVENT_ISSUED_STATUS_SUCCEESS: 'CLEAR_EVENT_ISSUED_STATUS_SUCCEESS',
// coupon actions // coupon actions
GET_PRODUCT_COUPON_INFO: "GET_PRODUCT_COUPON_INFO", GET_PRODUCT_COUPON_INFO: 'GET_PRODUCT_COUPON_INFO',
GET_PRODUCT_COUPON_SEARCH: "GET_PRODUCT_COUPON_SEARCH", GET_PRODUCT_COUPON_SEARCH: 'GET_PRODUCT_COUPON_SEARCH',
GET_PRODUCT_COUPON_DOWNLOAD: "GET_PRODUCT_COUPON_DOWNLOAD", GET_PRODUCT_COUPON_DOWNLOAD: 'GET_PRODUCT_COUPON_DOWNLOAD',
GET_PRODUCT_COUPON_TOTDOWNLOAD: "GET_PRODUCT_COUPON_TOTDOWNLOAD", GET_PRODUCT_COUPON_TOTDOWNLOAD: 'GET_PRODUCT_COUPON_TOTDOWNLOAD',
CLEAR_COUPON_INFO: "CLEAR_COUPON_INFO", CLEAR_COUPON_INFO: 'CLEAR_COUPON_INFO',
CLEAR_GET_PRODUCT_COUPON_DOWNLOAD: "CLEAR_GET_PRODUCT_COUPON_DOWNLOAD", CLEAR_GET_PRODUCT_COUPON_DOWNLOAD: 'CLEAR_GET_PRODUCT_COUPON_DOWNLOAD',
// checkout actions // checkout actions
GET_CHECKOUT_INFO: "GET_CHECKOUT_INFO", GET_CHECKOUT_INFO: 'GET_CHECKOUT_INFO',
INSERT_MY_INFO_CHECKOUT_ORDER: "INSERT_MY_INFO_CHECKOUT_ORDER", INSERT_MY_INFO_CHECKOUT_ORDER: 'INSERT_MY_INFO_CHECKOUT_ORDER',
GET_TAX_INFOS: "GET_TAX_INFOS", GET_TAX_INFOS: 'GET_TAX_INFOS',
UPDATE_SELECTED_SHIPPING_ADDR: "UPDATE_SELECTED_SHIPPING_ADDR", UPDATE_SELECTED_SHIPPING_ADDR: 'UPDATE_SELECTED_SHIPPING_ADDR',
UPDATE_SELECTED_BILLING_ADDR: "UPDATE_SELECTED_BILLING_ADDR", UPDATE_SELECTED_BILLING_ADDR: 'UPDATE_SELECTED_BILLING_ADDR',
UPDATE_SELECTED_COUPON: "UPDATE_SELECTED_COUPON", UPDATE_SELECTED_COUPON: 'UPDATE_SELECTED_COUPON',
GET_CHECKOUT_TOTAL_AMT: "GET_CHECKOUT_TOTAL_AMT", GET_CHECKOUT_TOTAL_AMT: 'GET_CHECKOUT_TOTAL_AMT',
CHECKOUT_DATA_RESET: "CHECKOUT_DATA_RESET", CHECKOUT_DATA_RESET: 'CHECKOUT_DATA_RESET',
// order actions // order actions
SET_PURCHASE_TERMS_AGREE: "SET_PURCHASE_TERMS_AGREE", SET_PURCHASE_TERMS_AGREE: 'SET_PURCHASE_TERMS_AGREE',
SET_PURCHASE_TERMS_WITHDRAW: "SET_PURCHASE_TERMS_WITHDRAW", SET_PURCHASE_TERMS_WITHDRAW: 'SET_PURCHASE_TERMS_WITHDRAW',
GET_MY_INFO_ORDER_SEARCH: "GET_MY_INFO_ORDER_SEARCH", GET_MY_INFO_ORDER_SEARCH: 'GET_MY_INFO_ORDER_SEARCH',
GET_MY_INFO_ORDER_SEARCH_CANCEL: "GET_MY_INFO_ORDER_SEARCH_CANCEL", GET_MY_INFO_ORDER_SEARCH_CANCEL: 'GET_MY_INFO_ORDER_SEARCH_CANCEL',
GET_MY_INFO_ORDER_DETAIL_SEARCH: "GET_MY_INFO_ORDER_DETAIL_SEARCH", GET_MY_INFO_ORDER_DETAIL_SEARCH: 'GET_MY_INFO_ORDER_DETAIL_SEARCH',
GET_MY_INFO_ORDER_SHIPPING_SEARCH: "GET_MY_INFO_ORDER_SHIPPING_SEARCH", GET_MY_INFO_ORDER_SHIPPING_SEARCH: 'GET_MY_INFO_ORDER_SHIPPING_SEARCH',
CLEAR_MY_INFO_ORDER_SEARCH: "CLEAR_MY_INFO_ORDER_SEARCH", CLEAR_MY_INFO_ORDER_SEARCH: 'CLEAR_MY_INFO_ORDER_SEARCH',
// play controller // play controller
GET_CHAT_LOG: "GET_CHAT_LOG", GET_CHAT_LOG: 'GET_CHAT_LOG',
GET_SUBTITLE: "GET_SUBTITLE", GET_SUBTITLE: 'GET_SUBTITLE',
CLEAR_PLAYER_INFO: "CLEAR_PLAYER_INFO", CLEAR_PLAYER_INFO: 'CLEAR_PLAYER_INFO',
UPDATE_VIDEO_PLAY_STATE: 'UPDATE_VIDEO_PLAY_STATE',
// 🔽 [추가] 플레이 제어 매니저 액션 타입 // 🔽 [추가] 플레이 제어 매니저 액션 타입
/** /**
@@ -224,58 +225,57 @@ export const types = {
* SET_PLAYER_CONTROL: 특정 컴포넌트에게 비디오 재생 제어권을 부여합니다. * SET_PLAYER_CONTROL: 특정 컴포넌트에게 비디오 재생 제어권을 부여합니다.
* CLEAR_PLAYER_CONTROL: 컴포넌트로부터 비디오 재생 제어권을 회수합니다. * CLEAR_PLAYER_CONTROL: 컴포넌트로부터 비디오 재생 제어권을 회수합니다.
*/ */
SET_PLAYER_CONTROL: "SET_PLAYER_CONTROL", SET_PLAYER_CONTROL: 'SET_PLAYER_CONTROL',
CLEAR_PLAYER_CONTROL: "CLEAR_PLAYER_CONTROL", CLEAR_PLAYER_CONTROL: 'CLEAR_PLAYER_CONTROL',
// reset action // reset action
RESET_REDUX_STATE: "RESET_REDUX_STATE", RESET_REDUX_STATE: 'RESET_REDUX_STATE',
// cancel actions // cancel actions
GET_MY_INFO_ORDER_CANCEL_SEARCH: "GET_MY_INFO_ORDER_CANCEL_SEARCH", GET_MY_INFO_ORDER_CANCEL_SEARCH: 'GET_MY_INFO_ORDER_CANCEL_SEARCH',
GET_MY_INFO_ORDER_CANCEL_COLUMNS_SEARCH: GET_MY_INFO_ORDER_CANCEL_COLUMNS_SEARCH: 'GET_MY_INFO_ORDER_CANCEL_COLUMNS_SEARCH',
"GET_MY_INFO_ORDER_CANCEL_COLUMNS_SEARCH", UPDATE_ORDER_PARTIAL_CANCEL: 'UPDATE_ORDER_PARTIAL_CANCEL',
UPDATE_ORDER_PARTIAL_CANCEL: "UPDATE_ORDER_PARTIAL_CANCEL", PAYMENT_TOTAL_CANCEL: 'PAYMENT_TOTAL_CANCEL',
PAYMENT_TOTAL_CANCEL: "PAYMENT_TOTAL_CANCEL",
// emp actions // emp actions
GET_SHOPTIME_TERMS: "GET_SHOPTIME_TERMS", GET_SHOPTIME_TERMS: 'GET_SHOPTIME_TERMS',
// pinCode actions // pinCode actions
GET_MY_INFO_CARD_PINCODE_CHECK: "GET_MY_INFO_CARD_PINCODE_CHECK", GET_MY_INFO_CARD_PINCODE_CHECK: 'GET_MY_INFO_CARD_PINCODE_CHECK',
// new actions // new actions
CANCEL_FOCUS_ELEMENT: "CANCEL_FOCUS_ELEMENT", CANCEL_FOCUS_ELEMENT: 'CANCEL_FOCUS_ELEMENT',
// 약관동의 여부 확인 상태 // 약관동의 여부 확인 상태
GET_TERMS_AGREE_YN_START: "GET_TERMS_AGREE_YN_START", GET_TERMS_AGREE_YN_START: 'GET_TERMS_AGREE_YN_START',
GET_TERMS_AGREE_YN_SUCCESS: "GET_TERMS_AGREE_YN_SUCCESS", GET_TERMS_AGREE_YN_SUCCESS: 'GET_TERMS_AGREE_YN_SUCCESS',
GET_TERMS_AGREE_YN_FAILURE: "GET_TERMS_AGREE_YN_FAILURE", GET_TERMS_AGREE_YN_FAILURE: 'GET_TERMS_AGREE_YN_FAILURE',
// device // device
REQ_REG_DEVICE_INFO: "REQ_REG_DEVICE_INFO", REQ_REG_DEVICE_INFO: 'REQ_REG_DEVICE_INFO',
// 🔽 [추가] 영구재생 비디오 정보 저장 // 🔽 [추가] 영구재생 비디오 정보 저장
SET_PERSISTENT_VIDEO_INFO: "SET_PERSISTENT_VIDEO_INFO", SET_PERSISTENT_VIDEO_INFO: 'SET_PERSISTENT_VIDEO_INFO',
// 🔽 [추가] 배너 비디오 제어 액션 타입 // 🔽 [추가] 배너 비디오 제어 액션 타입
/** /**
* HomeBanner의 배너 간 비디오 재생 제어를 위한 액션 타입들. * HomeBanner의 배너 간 비디오 재생 제어를 위한 액션 타입들.
* 첫 번째 배너 상시 재생과 두 번째 배너 포커스 재생을 관리합니다. * 첫 번째 배너 상시 재생과 두 번째 배너 포커스 재생을 관리합니다.
*/ */
SET_BANNER_STATE: "SET_BANNER_STATE", SET_BANNER_STATE: 'SET_BANNER_STATE',
SET_BANNER_FOCUS: "SET_BANNER_FOCUS", SET_BANNER_FOCUS: 'SET_BANNER_FOCUS',
SET_BANNER_AVAILABILITY: "SET_BANNER_AVAILABILITY", SET_BANNER_AVAILABILITY: 'SET_BANNER_AVAILABILITY',
SET_BANNER_TRANSITION: "SET_BANNER_TRANSITION", SET_BANNER_TRANSITION: 'SET_BANNER_TRANSITION',
PAUSE_PLAYER_CONTROL: "PAUSE_PLAYER_CONTROL", PAUSE_PLAYER_CONTROL: 'PAUSE_PLAYER_CONTROL',
RESUME_PLAYER_CONTROL: "RESUME_PLAYER_CONTROL", RESUME_PLAYER_CONTROL: 'RESUME_PLAYER_CONTROL',
// 🔽 [추가] HomeBanner 동영상 포커스 정책 관리 // 🔽 [추가] HomeBanner 동영상 포커스 정책 관리
SET_CURRENT_FOCUS_BANNER: "SET_CURRENT_FOCUS_BANNER", SET_CURRENT_FOCUS_BANNER: 'SET_CURRENT_FOCUS_BANNER',
UPDATE_VIDEO_POLICY: "UPDATE_VIDEO_POLICY", UPDATE_VIDEO_POLICY: 'UPDATE_VIDEO_POLICY',
SET_MODAL_BORDER: "SET_MODAL_BORDER", SET_MODAL_BORDER: 'SET_MODAL_BORDER',
SET_BANNER_VISIBILITY: "SET_BANNER_VISIBILITY", SET_BANNER_VISIBILITY: 'SET_BANNER_VISIBILITY',
// 🔽 [추가] JustForYou 상품 관리 부분 // 🔽 [추가] JustForYou 상품 관리 부분
GET_RECENTLY_SAW_ITEM: "GET_RECENTLY_SAW_ITEM", GET_RECENTLY_SAW_ITEM: 'GET_RECENTLY_SAW_ITEM',
GET_LIKE_BRAND_PRODUCT: "GET_LIKE_BRAND_PRODUCT", GET_LIKE_BRAND_PRODUCT: 'GET_LIKE_BRAND_PRODUCT',
GET_MORE_TO_CONCIDER_AT_THIS_PRICE: "GET_MORE_TO_CONCIDER_AT_THIS_PRICE", GET_MORE_TO_CONCIDER_AT_THIS_PRICE: 'GET_MORE_TO_CONCIDER_AT_THIS_PRICE',
}; };

View File

@@ -135,6 +135,52 @@ export const resumeModalVideo = () => (dispatch, getState) => {
} }
}; };
// 전체화면 비디오를 일시정지 (패널은 유지)
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) {
console.log('[pauseFullscreenVideo] Pausing fullscreen video');
dispatch(
updatePanel({
name: panel_names.PLAYER_PANEL,
panelInfo: {
...fullscreenPlayerPanel.panelInfo,
isPaused: true, // 일시정지 플래그 추가
},
})
);
}
};
// 전체화면 비디오를 재생 (일시정지 해제)
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
);
if (fullscreenPlayerPanel && fullscreenPlayerPanel.panelInfo?.isPaused) {
console.log('[resumeFullscreenVideo] Resuming fullscreen video');
dispatch(
updatePanel({
name: panel_names.PLAYER_PANEL,
panelInfo: {
...fullscreenPlayerPanel.panelInfo,
isPaused: false, // 일시정지 해제
},
})
);
}
};
// 채팅 로그 가져오기 IF-LGSP-371 // 채팅 로그 가져오기 IF-LGSP-371
export const getChatLog = export const getChatLog =
({ patnrId, showId }) => ({ patnrId, showId }) =>
@@ -187,6 +233,20 @@ export const CLEAR_PLAYER_INFO = () => ({
type: types.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,
});
/* 🔽 [추가] 새로운 '플레이 제어 매니저' 액션들 */ /* 🔽 [추가] 새로운 '플레이 제어 매니저' 액션들 */
/** /**

View File

@@ -64,6 +64,7 @@ import Overlay from './Overlay';
import TReactPlayer from './TReactPlayer'; import TReactPlayer from './TReactPlayer';
import Video from './Video'; import Video from './Video';
import css from './VideoPlayer.module.less'; import css from './VideoPlayer.module.less';
import { updateVideoPlayState } from '../../actions/playActions';
const isEnter = is('enter'); const isEnter = is('enter');
const isLeft = is('left'); const isLeft = is('left');
@@ -714,6 +715,7 @@ const VideoPlayerBase = class extends React.Component {
belowContentsVisible: PropTypes.bool, belowContentsVisible: PropTypes.bool,
tabContainerVersion: PropTypes.number, tabContainerVersion: PropTypes.number,
tabIndexV2: PropTypes.number, tabIndexV2: PropTypes.number,
dispatch: PropTypes.func,
}; };
static contextType = FloatingLayerContext; static contextType = FloatingLayerContext;
@@ -1507,6 +1509,19 @@ const VideoPlayerBase = class extends React.Component {
updatedState.thumbnailUrl = null; updatedState.thumbnailUrl = null;
} }
this.setState(updatedState); this.setState(updatedState);
// Redux에 비디오 재생 상태 업데이트
if (this.props.dispatch) {
this.props.dispatch(
updateVideoPlayState({
isPlaying: !updatedState.paused,
isPaused: updatedState.paused,
currentTime: updatedState.currentTime,
duration: updatedState.duration,
playbackRate: updatedState.playbackRate,
})
);
}
}; };
renderBottomControl = new Job(() => { renderBottomControl = new Job(() => {

View File

@@ -1,8 +1,15 @@
import { types } from "../actions/actionTypes"; import { types } from '../actions/actionTypes';
const initialState = { const initialState = {
subTitleBlobs: {}, subTitleBlobs: {},
chatData: null, chatData: null,
videoPlayState: {
isPlaying: false,
isPaused: true,
currentTime: 0,
duration: 0,
playbackRate: 1,
},
}; };
export const playReducer = (state = initialState, action) => { export const playReducer = (state = initialState, action) => {
@@ -19,14 +26,13 @@ export const playReducer = (state = initialState, action) => {
...state, ...state,
subTitleBlobs: { subTitleBlobs: {
...state.subTitleBlobs, ...state.subTitleBlobs,
[action.payload.url]: "Error", [action.payload.url]: 'Error',
}, },
}; };
} }
var srtRegex = var srtRegex = /(.*\n)?(\d\d:\d\d:\d\d),(\d\d\d --> \d\d:\d\d:\d\d),(\d\d\d)/g;
/(.*\n)?(\d\d:\d\d:\d\d),(\d\d\d --> \d\d:\d\d:\d\d),(\d\d\d)/g; var vttText = 'WEBVTT\n\n' + srtText.replace(srtRegex, '$1$2.$3.$4');
var vttText = "WEBVTT\n\n" + srtText.replace(srtRegex, "$1$2.$3.$4"); var vttBlob = new Blob([vttText], { type: 'text/vtt' });
var vttBlob = new Blob([vttText], { type: "text/vtt" });
let blobUrl = URL.createObjectURL(vttBlob); let blobUrl = URL.createObjectURL(vttBlob);
return { return {
@@ -43,6 +49,15 @@ export const playReducer = (state = initialState, action) => {
chatData: {}, chatData: {},
}; };
} }
case types.UPDATE_VIDEO_PLAY_STATE: {
return {
...state,
videoPlayState: {
...state.videoPlayState,
...action.payload,
},
};
}
default: default:
return state; return state;

View File

@@ -84,82 +84,6 @@ export default function HomeBanner({ firstSpot, spotlightId, handleItemFocus, ha
} }
}, [handleItemFocus]); }, [handleItemFocus]);
// 🔽 [추가] 0번째 배너에 대한 영구 비디오 재생을 처리하는 useEffect
// useEffect(() => {
// if (
// bannerDataList &&
// bannerDataList.length > 0 &&
// selectTemplate === "DSP00201"
// ) {
// const banner0Data = bannerDataList[0]?.bannerDetailInfos?.[0];
// if (banner0Data && banner0Data.showUrl) {
// // DOM 요소가 존재하는지 확인 후 실행
// console.log("[HomeBanner] banner0Data", banner0Data);
// const checkAndPlay = () => {
// const targetElement = document.querySelector(
// '[data-spotlight-id="banner0"]',
// );
// console.log("[HomeBanner] targetElement", targetElement);
// if (targetElement) {
// console.log("[HomeBanner] targetElement 존재");
// dispatch(
// startVideoPlayer({
// showUrl: banner0Data.showUrl,
// patnrId: banner0Data.patnrId,
// showId: banner0Data.showId,
// shptmBanrTpNm: "MEDIA",
// modal: true,
// modalContainerId: "banner0",
// spotlightDisable: true,
// }),
// );
// console.log("[HomeBanner] startVideoPlayer 호출");
// } else {
// // 요소가 없으면 잠시 후 재시도
// console.log("[HomeBanner] targetElement 없음");
// setTimeout(checkAndPlay, 100);
// }
// };
// // 다음 tick에서 실행하여 렌더링 완료 보장
// setTimeout(checkAndPlay, 0);
// }
// }
// }, [dispatch, bannerDataList, selectTemplate]);
// 🔽 [수정] 새로운 1번 배너 포커스/블러 핸들러
const handleSecondBannerFocus = useCallback(() => {
const secondBannerData = bannerDataList?.[1];
if (secondBannerData) {
const randomData = secondBannerData.bannerDetailInfos[secondBannerData.randomIndex];
const videoInfo = {
showUrl: randomData.showUrl,
patnrId: randomData.patnrId,
showId: randomData.showId,
shptmBanrTpNm: randomData.showId ? randomData.shptmBanrTpNm : 'MEDIA',
lgCatCd: randomData.lgCatCd,
chanId: randomData.brdcChnlId,
modal: true,
modalContainerId: 'banner1',
modalClassName: css.videoModal,
isVerticalModal: true, // Assuming second banner is horizontal, so modal is vertical
};
// 중앙 제어 시스템에 '미리보기' 재생을 요청
dispatch(requestPlayControl('banner1_preview', videoInfo));
}
if (handleItemFocus) {
handleItemFocus();
}
}, [dispatch, bannerDataList, handleItemFocus]);
const handleSecondBannerBlur = useCallback(() => {
// 중앙 제어 시스템에 '미리보기' 제어권 해제를 요청
dispatch(releasePlayControl('banner1_preview'));
}, [dispatch]);
const termsData = useSelector((state) => state.home.termsData); const termsData = useSelector((state) => state.home.termsData);
const termsIdMap = useSelector((state) => state.home.termsIdMap); const termsIdMap = useSelector((state) => state.home.termsIdMap);
const optionalTermsAvailable = useSelector((state) => state.home.optionalTermsAvailable); const optionalTermsAvailable = useSelector((state) => state.home.optionalTermsAvailable);
@@ -379,27 +303,6 @@ export default function HomeBanner({ firstSpot, spotlightId, handleItemFocus, ha
return null; return null;
}, [bannerDataList]); }, [bannerDataList]);
// useEffect(() => {
// if (firstSpot && defaultFocus && popupVisible === false) {
// setTimeout(() => {
// Spotlight.focus(defaultFocus);
// }, 0);
// }
// if (defaultFocus) {
// dispatch(setDefaultFocus(defaultFocus));
// }
// }, [defaultFocus, dispatch, popupVisible]);
// 테스트용 팝업 표시
// useEffect(() => {
// setTimeout(() => {
// console.log("App.js optionalTermsTest 팝업 표시");
// setIsOptionalConfirmVisible(true);
// // setIsOptionalTermsVisible(true);
// }, 1000);
// }, []);
// 약관 동의 및 선택 약관 팝업 처리 (TV 환경 최적화) // 약관 동의 및 선택 약관 팝업 처리 (TV 환경 최적화)
useEffect(() => { useEffect(() => {
if (termsLoading) { if (termsLoading) {
@@ -423,77 +326,6 @@ export default function HomeBanner({ firstSpot, spotlightId, handleItemFocus, ha
} }
}, [shouldShowOptionalTermsPopup, termsLoading, isOptionalConfirmVisible, dispatch]); }, [shouldShowOptionalTermsPopup, termsLoading, isOptionalConfirmVisible, dispatch]);
// const renderItem = useCallback(
// (index, isHorizontal) => {
// const data = bannerDataList?.[index] ?? {};
// if (index === 1) {
// return (
// <div className={!isHorizontal ? css.imgBox : undefined}>
// <RandomUnitNew
// bannerData={data}
// isHorizontal={isHorizontal}
// key={"banner" + index}
// spotlightId={"banner" + index}
// handleShelfFocus={_handleShelfFocus}
// onFocus={handleSecondBannerFocus}
// onBlur={handleSecondBannerBlur}
// randomNumber={data.randomIndex}
// />
// </div>
// );
// }
// return (
// <div className={!isHorizontal ? css.imgBox : undefined}>
// {data.shptmDspyTpNm === "Rolling" ? (
// <Rolling
// bannerData={data}
// isHorizontal={isHorizontal}
// key={"banner" + index}
// spotlightId={"banner" + index}
// handleShelfFocus={_handleShelfFocus}
// handleItemFocus={_handleItemFocus}
// />
// ) : data.shptmDspyTpNm === "Random" ? (
// <Random
// bannerData={data}
// isHorizontal={isHorizontal}
// key={"banner" + index}
// spotlightId={"banner" + index}
// handleShelfFocus={_handleShelfFocus}
// handleItemFocus={_handleItemFocus}
// randomNumber={data.randomIndex}
// />
// ) : (
// <SpottableComponent spotlightId={"banner" + index}>
// <CustomImage
// delay={0}
// src={
// isHorizontal
// ? homeTopDisplayInfo.wdthtpImgPath1
// : homeTopDisplayInfo.vtctpImgPath1
// }
// aria-label={
// isHorizontal
// ? homeTopDisplayInfo.wdthtpImgNm1
// : homeTopDisplayInfo.vtctpImgNm1
// }
// />
// </SpottableComponent>
// )}
// </div>
// );
// },
// [
// bannerDataList,
// _handleItemFocus,
// _handleShelfFocus,
// handleSecondBannerFocus,
// handleSecondBannerBlur,
// ],
// );
const renderItem = useCallback( const renderItem = useCallback(
(index, isHorizontal, videoPlayerable = false) => { (index, isHorizontal, videoPlayerable = false) => {
const data = bannerDataList?.[index] ?? {}; const data = bannerDataList?.[index] ?? {};
@@ -678,7 +510,7 @@ export default function HomeBanner({ firstSpot, spotlightId, handleItemFocus, ha
} }
} }
return null; return null;
}, [selectTemplate, renderItem, renderSimpleVideoContainer]); }, [selectTemplate, renderItem, renderSimpleVideoContainer, introTermsAgree]);
return ( return (
<> <>

View File

@@ -254,6 +254,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
const liveShowInfos = USE_SELECTOR('liveShowInfos', (state) => state.main.liveShowInfos); const liveShowInfos = USE_SELECTOR('liveShowInfos', (state) => state.main.liveShowInfos);
const vodSubtitleData = USE_SELECTOR('vodSubtitleData', (state) => state.play.subTitleBlobs); const vodSubtitleData = USE_SELECTOR('vodSubtitleData', (state) => state.play.subTitleBlobs);
const broadcast = USE_SELECTOR('broadcast', (state) => state.common.broadcast); const broadcast = USE_SELECTOR('broadcast', (state) => state.common.broadcast);
const videoPlayState = USE_SELECTOR('videoPlayState', (state) => state.play.videoPlayState);
const lastPanelAction = USE_SELECTOR('lastPanelAction', (state) => state.panels.lastPanelAction); const lastPanelAction = USE_SELECTOR('lastPanelAction', (state) => state.panels.lastPanelAction);
const nowMenu = USE_SELECTOR('nowMenu', (state) => state.common.menu.nowMenu); const nowMenu = USE_SELECTOR('nowMenu', (state) => state.common.menu.nowMenu);
@@ -2215,6 +2216,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
broadcast={broadcast} broadcast={broadcast}
tabContainerVersion={tabContainerVersion} tabContainerVersion={tabContainerVersion}
tabIndexV2={tabIndexV2} tabIndexV2={tabIndexV2}
dispatch={dispatch}
> >
{typeof window === 'object' && window.PalmSystem && ( {typeof window === 'object' && window.PalmSystem && (
<source src={currentPlayingUrl} type={videoType} /> <source src={currentPlayingUrl} type={videoType} />