diff --git a/.docs/modal-transition-analysis.md b/.docs/modal-transition-analysis.md new file mode 100644 index 00000000..3707992d --- /dev/null +++ b/.docs/modal-transition-analysis.md @@ -0,0 +1,437 @@ +# Modal 전환 기능 상세 분석 + +**작성일**: 2025-11-10 +**목적**: MediaPlayer.v2.jsx 설계를 위한 필수 기능 분석 + +--- + +## 📋 Modal 모드 전환 플로우 + +### 1. 시작: Modal 모드로 비디오 재생 + +```javascript +// actions/mediaActions.js - startMediaPlayer() +dispatch(startMediaPlayer({ + modal: true, + modalContainerId: 'some-product-id', + showUrl: 'video-url.mp4', + thumbnailUrl: 'thumb.jpg', + // ... +})); +``` + +**MediaPanel에서의 처리 (MediaPanel.jsx:114-161)**: +```javascript +useEffect(() => { + if (panelInfo.modal && panelInfo.modalContainerId) { + // 1. DOM 노드 찾기 + const node = document.querySelector( + `[data-spotlight-id="${panelInfo.modalContainerId}"]` + ); + + // 2. 위치와 크기 계산 + const { width, height, top, left } = node.getBoundingClientRect(); + + // 3. padding/margin 조정 + const totalOffset = 24; // 6*2 + 6*2 + const adjustedWidth = width - totalOffset; + const adjustedHeight = height - totalOffset; + + // 4. Fixed 위치 스타일 생성 + const style = { + width: adjustedWidth + 'px', + height: adjustedHeight + 'px', + top: (top + totalOffset/2) + 'px', + left: (left + totalOffset/2) + 'px', + position: 'fixed', + overflow: 'hidden' + }; + + setModalStyle(style); + setModalScale(adjustedWidth / window.innerWidth); + } +}, [panelInfo, isOnTop]); +``` + +**VideoPlayer에 전달**: +```javascript + +``` + +--- + +### 2. 전환: Modal → Fullscreen + +**사용자 액션**: modal 비디오 클릭 + +```javascript +// MediaPanel.jsx:164-174 +const onVideoClick = useCallback(() => { + if (panelInfo.modal) { + dispatch(switchMediaToFullscreen()); + } +}, [dispatch, panelInfo.modal]); +``` + +**Redux Action (mediaActions.js:164-208)**: +```javascript +export const switchMediaToFullscreen = () => (dispatch, getState) => { + const modalMediaPanel = panels.find( + (panel) => panel.name === panel_names.MEDIA_PANEL && + panel.panelInfo?.modal + ); + + if (modalMediaPanel) { + dispatch(updatePanel({ + name: panel_names.MEDIA_PANEL, + panelInfo: { + ...modalMediaPanel.panelInfo, + modal: false // 🔑 핵심: modal만 false로 변경 + } + })); + } +}; +``` + +**MediaPanel 재렌더링**: +```javascript +// panelInfo.modal이 false가 되면 useEffect 재실행 +useEffect(() => { + // modal이 false이면 else if 분기 실행 + else if (isOnTop && !panelInfo.modal && !panelInfo.isMinimized && videoPlayer.current) { + // 재생 상태 복원 + if (videoPlayer.current?.getMediaState()?.paused) { + videoPlayer.current.play(); + } + + // controls 표시 + if (!videoPlayer.current.areControlsVisible()) { + videoPlayer.current.showControls(); + } + } +}, [panelInfo, isOnTop]); + +// VideoPlayer에 전달되는 props 변경 + +``` + +--- + +### 3. 복귀: Fullscreen → Modal (Back 버튼) + +```javascript +// MediaPanel.jsx:176-194 +const onClickBack = useCallback((ev) => { + // modalContainerId가 있으면 modal에서 왔던 것 + if (panelInfo.modalContainerId && !panelInfo.modal) { + dispatch(PanelActions.popPanel()); + ev?.stopPropagation(); + return; + } + + // 일반 fullscreen이면 그냥 닫기 + if (!panelInfo.modal) { + dispatch(PanelActions.popPanel()); + ev?.stopPropagation(); + } +}, [dispatch, panelInfo]); +``` + +--- + +## 🔑 핵심 메커니즘 + +### 1. 같은 MediaPanel 재사용 +- modal → fullscreen 전환 시 패널을 새로 만들지 않음 +- **updatePanel**로 `panelInfo.modal`만 변경 +- **비디오 재생 상태 유지** (같은 컴포넌트 인스턴스) + +### 2. 스타일 동적 계산 +```javascript +// modal=true +style={{ + position: 'fixed', + top: '100px', + left: '200px', + width: '400px', + height: '300px' +}} + +// modal=false +style={{}} // 전체화면 (기본 CSS) +``` + +### 3. Pause/Resume 관리 +```javascript +// modal에서 다른 패널이 위로 올라오면 +useEffect(() => { + if (panelInfo?.modal) { + if (!isOnTop) { + dispatch(pauseModalMedia()); // isPaused: true + } else if (isOnTop && panelInfo.isPaused) { + dispatch(resumeModalMedia()); // isPaused: false + } + } +}, [isOnTop, panelInfo, dispatch]); + +// VideoPlayer에서 isPaused 감지하여 play/pause 제어 +useEffect(() => { + if (panelInfo?.modal && videoPlayer.current) { + if (panelInfo.isPaused) { + videoPlayer.current.pause(); + } else if (panelInfo.isPaused === false) { + videoPlayer.current.play(); + } + } +}, [panelInfo?.isPaused, panelInfo?.modal]); +``` + +--- + +## 📐 MediaPlayer.v2.jsx가 지원해야 할 기능 + +### ✅ 필수 Props (추가) + +```javascript +{ + // 기존 + src, + autoPlay, + loop, + onEnded, + onError, + thumbnailUrl, + videoComponent, + + // Modal 전환 관련 (필수) + disabled, // modal=true일 때 true + spotlightDisabled, // modal=true일 때 true + onClick, // modal일 때 클릭 → switchMediaToFullscreen + style, // modal일 때 fixed position style + modalClassName, // modal일 때 추가 className + modalScale, // modal일 때 scale 값 (QR코드 등에 사용) + + // 패널 정보 + panelInfo: { + modal, // modal 모드 여부 + modalContainerId, // modal 기준 컨테이너 ID + isPaused, // 일시정지 여부 (다른 패널 위로 올라옴) + showUrl, // 비디오 URL + thumbnailUrl, // 썸네일 URL + }, + + // 콜백 + onBackButton, // Back 버튼 핸들러 + + // Spotlight + spotlightId, +} +``` + +### ✅ 필수 기능 + +#### 1. Modal 모드 스타일 적용 +```javascript +const containerStyle = useMemo(() => { + if (panelInfo?.modal && style) { + return style; // MediaPanel에서 계산한 fixed position + } + return {}; // 전체화면 +}, [panelInfo?.modal, style]); +``` + +#### 2. Modal 클릭 처리 +```javascript +const handleVideoClick = useCallback(() => { + if (panelInfo?.modal && onClick) { + onClick(); // switchMediaToFullscreen 호출 + return; + } + + // fullscreen이면 controls 토글 + toggleControls(); +}, [panelInfo?.modal, onClick]); +``` + +#### 3. isPaused 상태 동기화 +```javascript +useEffect(() => { + if (panelInfo?.modal && videoRef.current) { + if (panelInfo.isPaused) { + videoRef.current.pause(); + } else if (panelInfo.isPaused === false) { + videoRef.current.play(); + } + } +}, [panelInfo?.isPaused, panelInfo?.modal]); +``` + +#### 4. Modal → Fullscreen 전환 시 재생 복원 +```javascript +useEffect(() => { + // modal에서 fullscreen으로 전환되었을 때 + if (prevPanelInfo?.modal && !panelInfo?.modal) { + if (videoRef.current?.paused) { + videoRef.current.play(); + } + setControlsVisible(true); + } +}, [panelInfo?.modal]); +``` + +#### 5. Controls/Spotlight 비활성화 +```javascript +const shouldDisableControls = panelInfo?.modal || disabled; +const shouldDisableSpotlight = panelInfo?.modal || spotlightDisabled; +``` + +--- + +## 🚫 여전히 제거 가능한 기능 + +Modal 전환과 무관한 기능들: + +``` +❌ QR코드 오버레이 (PlayerPanel 전용) +❌ 전화번호 오버레이 (PlayerPanel 전용) +❌ 테마 인디케이터 (PlayerPanel 전용) +❌ MediaSlider (seek bar) - 단순 재생만 +❌ 복잡한 피드백 시스템 (miniFeedback, 8개 Job) +❌ Announce/Accessibility 복잡계 +❌ FloatingLayer +❌ Redux 통합 (updateVideoPlayState) +❌ TabContainer 동기화 (PlayerPanel 전용) +❌ MediaTitle, infoComponents +❌ jumpBy, fastForward, rewind +❌ playbackRate 조정 +``` + +--- + +## 📊 최종 상태 변수 (9개) + +```javascript +const [currentTime, setCurrentTime] = useState(0); +const [duration, setDuration] = useState(0); +const [paused, setPaused] = useState(true); +const [loading, setLoading] = useState(true); +const [error, setError] = useState(null); +const [controlsVisible, setControlsVisible] = useState(false); + +// Modal 관련 (MediaPanel에서 계산하므로 state 불필요) +// modalStyle, modalScale → props로 받음 +``` + +--- + +## 📊 최종 Props 목록 (~18개) + +```javascript +MediaPlayerV2.propTypes = { + // 비디오 소스 + src: PropTypes.string.isRequired, + type: PropTypes.string, + thumbnailUrl: PropTypes.string, + + // 재생 제어 + autoPlay: PropTypes.bool, + loop: PropTypes.bool, + + // Modal 전환 + disabled: PropTypes.bool, + spotlightDisabled: PropTypes.bool, + onClick: PropTypes.func, + style: PropTypes.object, + modalClassName: PropTypes.string, + modalScale: PropTypes.number, + + // 패널 정보 + panelInfo: PropTypes.shape({ + modal: PropTypes.bool, + modalContainerId: PropTypes.string, + isPaused: PropTypes.bool, + showUrl: PropTypes.string, + thumbnailUrl: PropTypes.string, + }), + + // 콜백 + onEnded: PropTypes.func, + onError: PropTypes.func, + onBackButton: PropTypes.func, + + // Spotlight + spotlightId: PropTypes.string, + + // 비디오 컴포넌트 + videoComponent: PropTypes.elementType, +}; +``` + +--- + +## 🎯 구현 우선순위 + +### Phase 1: 기본 재생 (1일) +- [ ] 비디오 element 렌더링 (Media / TReactPlayer) +- [ ] 기본 play/pause 제어 +- [ ] 로딩 상태 및 썸네일 표시 +- [ ] API 제공 (getMediaState, play, pause) + +### Phase 2: Modal 전환 (1일) +- [ ] Modal 스타일 적용 (props.style) +- [ ] Modal 클릭 → Fullscreen 전환 +- [ ] isPaused 상태 동기화 +- [ ] disabled/spotlightDisabled 처리 + +### Phase 3: Controls (1일) +- [ ] 최소한의 controls UI (재생/일시정지만) +- [ ] Controls 자동 숨김/보임 +- [ ] Spotlight 포커스 관리 (기본만) + +### Phase 4: 테스트 및 최적화 (1일) +- [ ] 메모리 프로파일링 +- [ ] 전환 애니메이션 부드럽게 +- [ ] Edge case 처리 + +--- + +## 💡 예상 개선 효과 (수정) + +| 항목 | 현재 | 개선 후 | 개선율 | +|------|------|---------|--------| +| **코드 라인** | 2,595 | ~700 | **73% 감소** | +| **상태 변수** | 20+ | 6~9 | **60% 감소** | +| **Props** | 70+ | ~18 | **74% 감소** | +| **타이머/Job** | 8 | 1~2 | **80% 감소** | +| **필수 기능** | 100% | 100% | **유지** | +| **메모리 점유** | 높음 | 낮음 | **예상 40%+ 감소** | +| **렌더링 속도** | 느림 | 빠름 | **예상 2배 향상** | + +--- + +## ✅ 결론 + +Modal 전환 기능은 복잡해 보이지만, 실제로는: +1. **MediaPanel**에서 스타일 계산 (modalStyle, modalScale) +2. **MediaPlayer**는 받은 style을 그대로 적용 +3. **modal 플래그**에 따라 controls/spotlight 활성화 여부만 제어 + +따라서 MediaPlayer.v2.jsx는: +- Modal 전환 로직 구현 필요 없음 +- Props 받아서 적용만 하면 됨 +- 핵심 복잡도는 MediaPanel에 있음 + +**→ 여전히 대폭 간소화 가능!** diff --git a/.docs/video-player-analysis-and-optimization-plan.md b/.docs/video-player-analysis-and-optimization-plan.md new file mode 100644 index 00000000..f3db4859 --- /dev/null +++ b/.docs/video-player-analysis-and-optimization-plan.md @@ -0,0 +1,214 @@ +# 비디오 플레이어 분석 및 최적화 계획 + +**작성일**: 2025-11-10 +**대상**: MediaPlayer.v2.jsx 설계 + +--- + +## 📊 현재 구조 분석 + +### 1. 발견된 파일들 + +| 파일 | 경로 | 라인 수 | 타입 | +|------|------|---------|------| +| VideoPlayer.js | `src/components/VideoPlayer/VideoPlayer.js` | 2,658 | Class Component | +| MediaPlayer.jsx | `src/components/VideoPlayer/MediaPlayer.jsx` | 2,595 | Class Component | +| MediaPanel.jsx | `src/views/MediaPanel/MediaPanel.jsx` | 415 | Function Component | +| PlayerPanel.jsx | `src/views/PlayerPanel/PlayerPanel.jsx` | 25,146+ | (파일 읽기 실패) | + +### 2. 주요 문제점 + +#### 🔴 심각한 코드 비대화 +``` +VideoPlayer.js: 2,658 라인 (클래스 컴포넌트) +MediaPlayer.jsx: 2,595 라인 (거의 동일한 복사본) +PlayerPanel.jsx: 25,146+ 라인 +``` + +#### 🔴 과도한 Enact 프레임워크 의존성 +```javascript +// 7개 이상의 Decorator 래핑 +ApiDecorator +I18nContextDecorator +Slottable +FloatingLayerDecorator +Skinnable +SpotlightContainerDecorator +Spottable, Touchable +``` + +#### 🔴 복잡한 상태 관리 (20+ 상태 변수) +```javascript +state = { + // 미디어 상태 + currentTime, duration, paused, loading, error, + playbackRate, proportionLoaded, proportionPlayed, + + // UI 상태 + announce, feedbackVisible, feedbackAction, + mediaControlsVisible, mediaSliderVisible, miniFeedbackVisible, + titleVisible, infoVisible, bottomControlsRendered, + + // 기타 + sourceUnavailable, titleOffsetHeight, bottomOffsetHeight, + lastFocusedTarget, slider5WayPressed, thumbnailUrl +} +``` + +#### 🔴 메모리 점유 과다 + +**8개의 Job 인스턴스**: +- `autoCloseJob` - 자동 controls 숨김 +- `hideTitleJob` - 타이틀 숨김 +- `hideFeedbackJob` - 피드백 숨김 +- `hideMiniFeedbackJob` - 미니 피드백 숨김 +- `rewindJob` - 되감기 처리 +- `announceJob` - 접근성 알림 +- `renderBottomControl` - 하단 컨트롤 렌더링 +- `slider5WayPressJob` - 슬라이더 5-way 입력 + +**다수의 이벤트 리스너**: +- `mousemove`, `touchmove`, `keydown`, `wheel` +- 복잡한 Spotlight 포커스 시스템 + +#### 🔴 불필요한 기능들 (MediaPanel에서 미사용) +```javascript +// PlayerOverlayQRCode (QR코드 표시) +// VideoOverlayWithPhoneNumber (전화번호 오버레이) +// ThemeIndicatorArrow (테마 인디케이터) +// FeedbackTooltip, MediaTitle (주석 처리됨) +// 복잡한 TabContainerV2 동기화 +// Redux 통합 (updateVideoPlayState) +``` + +--- + +## 🔍 webOS 특정 기능 분석 + +### 필수 기능 + +#### 1. Spotlight 포커스 관리 +```javascript +// 리모컨 5-way 네비게이션 +SpotlightContainerDecorator +Spottable, Touchable +``` + +#### 2. Media 컴포넌트 (webOS 전용) +```javascript +videoComponent: window.PalmSystem ? Media : TReactPlayer +``` + +#### 3. playbackRate 네거티브 지원 +```javascript +if (platform.webos) { + this.video.playbackRate = pbNumber; // 음수 지원 (되감기) +} else { + // 브라우저: 수동 되감기 구현 + this.beginRewind(); +} +``` + +### 제거 가능한 기능 + +- FloatingLayer 시스템 +- 복잡한 announce/accessibility 시스템 +- Marquee 애니메이션 +- 다중 오버레이 시스템 +- Job 기반 타이머 → `setTimeout`으로 대체 가능 + +--- + +## 📐 MediaPlayer.v2.jsx 초기 설계 (수정 전) + +### 설계 원칙 +``` +1. 함수 컴포넌트 + React Hooks 사용 +2. 상태 최소화 (5~7개만) +3. Enact 의존성 최소화 (Spotlight 기본만) +4. 직접 video element 제어 +5. props 최소화 (15개 이하) +6. 단순한 controls UI +7. 메모리 효율성 우선 +``` + +### 최소 상태 (6개) +```javascript +const [currentTime, setCurrentTime] = useState(0); +const [duration, setDuration] = useState(0); +const [paused, setPaused] = useState(true); +const [loading, setLoading] = useState(true); +const [controlsVisible, setControlsVisible] = useState(false); +const [error, setError] = useState(null); +``` + +### 필수 Props (~12개) +```javascript +{ + src, // 비디오 URL + type, // 비디오 타입 + autoPlay, // 자동 재생 + loop, // 반복 재생 + disabled, // modal 상태 + onEnded, // 종료 콜백 + onError, // 에러 콜백 + onBackButton, // 뒤로가기 + thumbnailUrl, // 썸네일 + panelInfo, // 패널 정보 + spotlightId, // spotlight ID + videoComponent // Media or TReactPlayer +} +``` + +### 제거할 기능들 +``` +❌ QR코드 오버레이 +❌ 전화번호 오버레이 +❌ 테마 인디케이터 +❌ 복잡한 피드백 시스템 +❌ MediaSlider (seek bar) +❌ 자동 숨김/보임 Job 시스템 +❌ Announce/Accessibility 복잡계 +❌ FloatingLayer +❌ Redux 통합 +❌ TabContainer 동기화 +❌ 다중 overlay 시스템 +❌ MediaTitle, infoComponents +❌ jumpBy, fastForward, rewind +❌ playbackRate 조정 +``` + +--- + +## 📈 예상 개선 효과 + +| 항목 | 현재 | 개선 후 | 개선율 | +|------|------|---------|--------| +| **코드 라인** | 2,595 | ~500 | **80% 감소** | +| **상태 변수** | 20+ | 5~7 | **65% 감소** | +| **Props** | 70+ | ~12 | **83% 감소** | +| **타이머/Job** | 8 | 2~3 | **70% 감소** | +| **메모리 점유** | 높음 | 낮음 | **예상 50%+ 감소** | +| **렌더링 속도** | 느림 | 빠름 | **예상 2~3배 향상** | + +--- + +## 🚨 중요 요구사항 추가 + +### Modal 모드 전환 기능 (필수) + +사용자 피드백: +> "비디오 플레이어가 이렇게 복잡하게 된 데에는 다 이유가 있다. +> modal=true 모드에서 화면의 일부 크기로 재생이 되다가 +> 그 화면 그대로 키워서 modal=false로 전체화면으로 비디오를 재생하는 부분이 있어야 한다." + +**→ 이 기능은 반드시 유지되어야 함** + +--- + +## 📝 다음 단계 + +1. Modal 전환 기능 상세 분석 +2. 필수 기능 재정의 +3. MediaPlayer.v2.jsx 재설계 +4. 구현 우선순위 결정