- claude/ 브랜치에서 누락된 .docs 폴더 복원 완료 - dispatch-async 관련 문서 9개 파일 복원 * 01-problem.md, 02-solution-dispatch-helper.md * 03-solution-async-utils.md, 04-solution-queue-system.md * 05-usage-patterns.md, 06-setup-guide.md * 07-changelog.md, 08-troubleshooting.md, README.md - MediaPlayer.v2 관련 문서 4개 파일 복원 * MediaPlayer-v2-README.md, MediaPlayer-v2-Required-Changes.md * MediaPlayer-v2-Risk-Analysis.md, PR-MediaPlayer-v2.md - 기타 분석 문서 2개 파일 복원 * modal-transition-analysis.md, video-player-analysis-and-optimization-plan.md - .gitignore에서 .docs 항목 제거로 문서 추적 가능하도록 수정 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: GLM 4.6 <noreply@z.ai>
414 lines
9.1 KiB
Markdown
414 lines
9.1 KiB
Markdown
# 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)
|