feat: Implement optimized MediaPlayer.v2 for webOS
MediaPlayer.v2.jsx 최적화 비디오 플레이어 구현 - 함수 컴포넌트 + React Hooks 사용 - 코드 라인 85% 감소 (2,595 → 388) - 상태 변수 65% 감소 (20+ → 7) - Modal ↔ Fullscreen 전환 지원 - isPaused 상태 동기화 - 최소한의 Controls UI - 메모리 효율성 개선 (Job 8개 → setTimeout 1개) 주요 기능: - 기본 재생/일시정지 제어 - Modal 모드에서 fixed position 적용 - 클릭 시 Fullscreen 전환 - webOS Media / TReactPlayer 자동 선택 - API 제공 (play, pause, seek, getMediaState) - Spotlight 포커스 관리 제거된 기능: - MediaSlider (seek bar) - jumpBy, fastForward, rewind - 복잡한 피드백 시스템 - FloatingLayer, Redux 통합 문서: - .docs/MediaPlayer-v2-README.md: 사용법 및 API 문서
This commit is contained in:
413
.docs/MediaPlayer-v2-README.md
Normal file
413
.docs/MediaPlayer-v2-README.md
Normal file
@@ -0,0 +1,413 @@
|
||||
# MediaPlayer.v2 - 최적화된 비디오 플레이어
|
||||
|
||||
**위치**: `src/components/VideoPlayer/MediaPlayer.v2.jsx`
|
||||
|
||||
---
|
||||
|
||||
## 📊 개요
|
||||
|
||||
webOS 환경에 최적화된 경량 비디오 플레이어 컴포넌트입니다.
|
||||
기존 MediaPlayer.jsx의 핵심 기능은 유지하면서 불필요한 복잡도를 제거했습니다.
|
||||
|
||||
### 주요 개선사항
|
||||
|
||||
| 항목 | 기존 | v2 | 개선율 |
|
||||
|------|------|-----|--------|
|
||||
| **코드 라인** | 2,595 | 388 | **85%↓** |
|
||||
| **상태 변수** | 20+ | 7 | **65%↓** |
|
||||
| **Props** | 70+ | 18 | **74%↓** |
|
||||
| **타이머/Job** | 8 | 1 | **87%↓** |
|
||||
| **필수 기능** | 100% | 100% | **✅ 유지** |
|
||||
|
||||
---
|
||||
|
||||
## ✨ 주요 기능
|
||||
|
||||
### 1. Modal ↔ Fullscreen 전환
|
||||
```javascript
|
||||
// Modal 모드로 시작
|
||||
<MediaPlayerV2
|
||||
src="video.mp4"
|
||||
panelInfo={{ modal: true, modalContainerId: 'product-123' }}
|
||||
onClick={() => dispatch(switchMediaToFullscreen())}
|
||||
style={modalStyle} // MediaPanel에서 계산
|
||||
/>
|
||||
|
||||
// 클릭 시 자동으로 Fullscreen으로 전환
|
||||
```
|
||||
|
||||
### 2. 기본 재생 제어
|
||||
```javascript
|
||||
const playerRef = useRef();
|
||||
|
||||
// API 메서드
|
||||
playerRef.current.play();
|
||||
playerRef.current.pause();
|
||||
playerRef.current.seek(30);
|
||||
playerRef.current.getMediaState();
|
||||
playerRef.current.showControls();
|
||||
playerRef.current.hideControls();
|
||||
```
|
||||
|
||||
### 3. isPaused 동기화
|
||||
```javascript
|
||||
// Modal 모드에서 다른 패널이 위로 올라오면 자동 일시정지
|
||||
<MediaPlayerV2
|
||||
panelInfo={{
|
||||
modal: true,
|
||||
isPaused: true // 자동으로 pause() 호출
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
### 4. webOS / 브라우저 자동 감지
|
||||
```javascript
|
||||
// webOS: Media 컴포넌트
|
||||
// 브라우저: TReactPlayer
|
||||
// YouTube: TReactPlayer
|
||||
|
||||
// 자동으로 적절한 컴포넌트 선택
|
||||
<MediaPlayerV2 src="video.mp4" />
|
||||
<MediaPlayerV2 src="https://youtube.com/watch?v=xxx" />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📐 Props
|
||||
|
||||
### 필수 Props
|
||||
|
||||
```typescript
|
||||
interface MediaPlayerV2Props {
|
||||
// 비디오 소스 (필수)
|
||||
src: string;
|
||||
}
|
||||
```
|
||||
|
||||
### 선택 Props
|
||||
|
||||
```typescript
|
||||
interface MediaPlayerV2Props {
|
||||
// 비디오 설정
|
||||
type?: string; // 기본: 'video/mp4'
|
||||
thumbnailUrl?: string;
|
||||
|
||||
// 재생 제어
|
||||
autoPlay?: boolean; // 기본: false
|
||||
loop?: boolean; // 기본: false
|
||||
muted?: boolean; // 기본: false
|
||||
|
||||
// Modal 전환
|
||||
disabled?: boolean; // Modal에서 true
|
||||
spotlightDisabled?: boolean;
|
||||
onClick?: () => void; // Modal 클릭 시
|
||||
style?: CSSProperties; // Modal fixed position
|
||||
modalClassName?: string;
|
||||
modalScale?: number;
|
||||
|
||||
// 패널 정보
|
||||
panelInfo?: {
|
||||
modal?: boolean;
|
||||
modalContainerId?: string;
|
||||
isPaused?: boolean;
|
||||
};
|
||||
|
||||
// 콜백
|
||||
onEnded?: (e: Event) => void;
|
||||
onError?: (e: Event) => void;
|
||||
onBackButton?: (e: Event) => void;
|
||||
onLoadStart?: (e: Event) => void;
|
||||
onTimeUpdate?: (e: Event) => void;
|
||||
onLoadedData?: (e: Event) => void;
|
||||
onLoadedMetadata?: (e: Event) => void;
|
||||
onDurationChange?: (e: Event) => void;
|
||||
|
||||
// Spotlight
|
||||
spotlightId?: string; // 기본: 'mediaPlayerV2'
|
||||
|
||||
// 비디오 컴포넌트
|
||||
videoComponent?: React.ComponentType;
|
||||
|
||||
// ReactPlayer 설정
|
||||
reactPlayerConfig?: object;
|
||||
|
||||
// 기타
|
||||
children?: React.ReactNode; // <source>, <track> tags
|
||||
className?: string;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💻 사용 예제
|
||||
|
||||
### 기본 사용
|
||||
|
||||
```javascript
|
||||
import MediaPlayerV2 from '../components/VideoPlayer/MediaPlayer.v2';
|
||||
|
||||
function MyComponent() {
|
||||
return (
|
||||
<MediaPlayerV2
|
||||
src="https://example.com/video.mp4"
|
||||
autoPlay
|
||||
onEnded={() => console.log('Video ended')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Modal 모드 (MediaPanel에서 사용)
|
||||
|
||||
```javascript
|
||||
import MediaPlayerV2 from '../components/VideoPlayer/MediaPlayer.v2';
|
||||
|
||||
function MediaPanel({ panelInfo }) {
|
||||
const [modalStyle, setModalStyle] = useState({});
|
||||
|
||||
useEffect(() => {
|
||||
if (panelInfo.modal && panelInfo.modalContainerId) {
|
||||
const node = document.querySelector(
|
||||
`[data-spotlight-id="${panelInfo.modalContainerId}"]`
|
||||
);
|
||||
const rect = node.getBoundingClientRect();
|
||||
|
||||
setModalStyle({
|
||||
position: 'fixed',
|
||||
top: rect.top + 'px',
|
||||
left: rect.left + 'px',
|
||||
width: rect.width + 'px',
|
||||
height: rect.height + 'px',
|
||||
});
|
||||
}
|
||||
}, [panelInfo]);
|
||||
|
||||
const handleVideoClick = () => {
|
||||
if (panelInfo.modal) {
|
||||
dispatch(switchMediaToFullscreen());
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<MediaPlayerV2
|
||||
src={panelInfo.showUrl}
|
||||
thumbnailUrl={panelInfo.thumbnailUrl}
|
||||
disabled={panelInfo.modal}
|
||||
spotlightDisabled={panelInfo.modal}
|
||||
onClick={handleVideoClick}
|
||||
style={panelInfo.modal ? modalStyle : {}}
|
||||
panelInfo={panelInfo}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### API 사용
|
||||
|
||||
```javascript
|
||||
import { useRef } from 'react';
|
||||
import MediaPlayerV2 from '../components/VideoPlayer/MediaPlayer.v2';
|
||||
|
||||
function MyComponent() {
|
||||
const playerRef = useRef();
|
||||
|
||||
const handlePlay = () => {
|
||||
playerRef.current?.play();
|
||||
};
|
||||
|
||||
const handlePause = () => {
|
||||
playerRef.current?.pause();
|
||||
};
|
||||
|
||||
const handleSeek = (time) => {
|
||||
playerRef.current?.seek(time);
|
||||
};
|
||||
|
||||
const getState = () => {
|
||||
const state = playerRef.current?.getMediaState();
|
||||
console.log(state);
|
||||
// {
|
||||
// currentTime: 10.5,
|
||||
// duration: 120,
|
||||
// paused: false,
|
||||
// loading: false,
|
||||
// error: null,
|
||||
// playbackRate: 1,
|
||||
// proportionPlayed: 0.0875
|
||||
// }
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<MediaPlayerV2
|
||||
ref={playerRef}
|
||||
src="video.mp4"
|
||||
/>
|
||||
|
||||
<button onClick={handlePlay}>Play</button>
|
||||
<button onClick={handlePause}>Pause</button>
|
||||
<button onClick={() => handleSeek(30)}>Seek 30s</button>
|
||||
<button onClick={getState}>Get State</button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### webOS <source> 태그 사용
|
||||
|
||||
```javascript
|
||||
<MediaPlayerV2 src="video.mp4">
|
||||
<source src="video.mp4" type="video/mp4" />
|
||||
<track kind="subtitles" src="subtitles.vtt" default />
|
||||
</MediaPlayerV2>
|
||||
```
|
||||
|
||||
### YouTube 재생
|
||||
|
||||
```javascript
|
||||
<MediaPlayerV2
|
||||
src="https://www.youtube.com/watch?v=dQw4w9WgXcQ"
|
||||
reactPlayerConfig={{
|
||||
youtube: {
|
||||
playerVars: {
|
||||
controls: 0,
|
||||
autoplay: 1,
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 API 메서드
|
||||
|
||||
ref를 통해 다음 메서드에 접근할 수 있습니다:
|
||||
|
||||
```typescript
|
||||
interface MediaPlayerV2API {
|
||||
// 재생 제어
|
||||
play(): void;
|
||||
pause(): void;
|
||||
seek(timeIndex: number): void;
|
||||
|
||||
// 상태 조회
|
||||
getMediaState(): {
|
||||
currentTime: number;
|
||||
duration: number;
|
||||
paused: boolean;
|
||||
loading: boolean;
|
||||
error: Error | null;
|
||||
playbackRate: number;
|
||||
proportionPlayed: number;
|
||||
};
|
||||
|
||||
// Controls 제어
|
||||
showControls(): void;
|
||||
hideControls(): void;
|
||||
toggleControls(): void;
|
||||
areControlsVisible(): boolean;
|
||||
|
||||
// Video Node 접근
|
||||
getVideoNode(): HTMLVideoElement | ReactPlayerInstance;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 제거된 기능
|
||||
|
||||
다음 기능들은 MediaPanel 사용 케이스에 불필요하여 제거되었습니다:
|
||||
|
||||
```
|
||||
❌ MediaSlider (seek bar)
|
||||
❌ jumpBy, fastForward, rewind
|
||||
❌ playbackRate 조정
|
||||
❌ QR코드 오버레이
|
||||
❌ 전화번호 오버레이
|
||||
❌ 테마 인디케이터
|
||||
❌ 복잡한 피드백 시스템 (8개 Job → 1개 setTimeout)
|
||||
❌ FloatingLayer
|
||||
❌ Redux 통합
|
||||
❌ TabContainer 동기화
|
||||
❌ Announce/Accessibility 복잡계
|
||||
❌ MediaTitle, infoComponents
|
||||
```
|
||||
|
||||
필요하다면 기존 MediaPlayer.jsx를 사용하세요.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 성능
|
||||
|
||||
### 메모리 사용량
|
||||
|
||||
- **타이머**: 8개 Job → 1개 setTimeout
|
||||
- **이벤트 리스너**: 최소화 (video element events만)
|
||||
- **상태 변수**: 7개 (20+개에서 감소)
|
||||
|
||||
### 렌더링 성능
|
||||
|
||||
- **useMemo**: 계산 비용이 큰 값 캐싱
|
||||
- **useCallback**: 함수 재생성 방지
|
||||
- **조건부 렌더링**: 불필요한 DOM 요소 제거
|
||||
|
||||
---
|
||||
|
||||
## 🔄 마이그레이션 가이드
|
||||
|
||||
### 기존 MediaPlayer.jsx에서 마이그레이션
|
||||
|
||||
대부분의 props는 호환됩니다:
|
||||
|
||||
```javascript
|
||||
// 기존
|
||||
import { VideoPlayer } from '../components/VideoPlayer/MediaPlayer';
|
||||
|
||||
// 새로운
|
||||
import MediaPlayerV2 from '../components/VideoPlayer/MediaPlayer.v2';
|
||||
```
|
||||
|
||||
제거된 props:
|
||||
- `jumpBy`, `initialJumpDelay`, `jumpDelay`
|
||||
- `playbackRateHash`
|
||||
- `onFastForward`, `onRewind`, `onJumpBackward`, `onJumpForward`
|
||||
- `feedbackHideDelay`, `miniFeedbackHideDelay`
|
||||
- `noMediaSliderFeedback`, `noMiniFeedback`, `noSlider`
|
||||
- `title`, `infoComponents`
|
||||
- 기타 PlayerPanel 전용 props
|
||||
|
||||
---
|
||||
|
||||
## 📝 Notes
|
||||
|
||||
### Modal 전환 작동 방식
|
||||
|
||||
1. **MediaPanel**이 `getBoundingClientRect()`로 스타일 계산
|
||||
2. **MediaPlayerV2**는 받은 `style`을 그대로 적용
|
||||
3. `modal` 플래그에 따라 controls/spotlight 활성화 제어
|
||||
|
||||
→ **MediaPlayerV2는 전환 로직 구현 불필요**
|
||||
|
||||
### webOS 호환성
|
||||
|
||||
- `window.PalmSystem` 존재 시 `Media` 컴포넌트 사용
|
||||
- 브라우저에서는 `TReactPlayer` 사용
|
||||
- YouTube URL은 항상 `TReactPlayer` 사용
|
||||
|
||||
---
|
||||
|
||||
## 🐛 알려진 제약사항
|
||||
|
||||
1. **Seek bar 없음**: 단순 재생만 지원
|
||||
2. **빠르기 조정 없음**: 배속 재생 미지원
|
||||
3. **간단한 Controls**: 재생/일시정지 버튼만
|
||||
|
||||
복잡한 컨트롤이 필요하다면 기존 `MediaPlayer.jsx` 사용을 권장합니다.
|
||||
|
||||
---
|
||||
|
||||
## 📚 관련 문서
|
||||
|
||||
- [비디오 플레이어 분석 문서](.docs/video-player-analysis-and-optimization-plan.md)
|
||||
- [Modal 전환 상세 분석](.docs/modal-transition-analysis.md)
|
||||
Reference in New Issue
Block a user