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 문서
9.1 KiB
9.1 KiB
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 전환
// Modal 모드로 시작
<MediaPlayerV2
src="video.mp4"
panelInfo={{ modal: true, modalContainerId: 'product-123' }}
onClick={() => dispatch(switchMediaToFullscreen())}
style={modalStyle} // MediaPanel에서 계산
/>
// 클릭 시 자동으로 Fullscreen으로 전환
2. 기본 재생 제어
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 동기화
// Modal 모드에서 다른 패널이 위로 올라오면 자동 일시정지
<MediaPlayerV2
panelInfo={{
modal: true,
isPaused: true // 자동으로 pause() 호출
}}
/>
4. webOS / 브라우저 자동 감지
// webOS: Media 컴포넌트
// 브라우저: TReactPlayer
// YouTube: TReactPlayer
// 자동으로 적절한 컴포넌트 선택
<MediaPlayerV2 src="video.mp4" />
<MediaPlayerV2 src="https://youtube.com/watch?v=xxx" />
📐 Props
필수 Props
interface MediaPlayerV2Props {
// 비디오 소스 (필수)
src: string;
}
선택 Props
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;
}
💻 사용 예제
기본 사용
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에서 사용)
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 사용
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 태그 사용
<MediaPlayerV2 src="video.mp4">
<source src="video.mp4" type="video/mp4" />
<track kind="subtitles" src="subtitles.vtt" default />
</MediaPlayerV2>
YouTube 재생
<MediaPlayerV2
src="https://www.youtube.com/watch?v=dQw4w9WgXcQ"
reactPlayerConfig={{
youtube: {
playerVars: {
controls: 0,
autoplay: 1,
}
}
}}
/>
🔧 API 메서드
ref를 통해 다음 메서드에 접근할 수 있습니다:
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는 호환됩니다:
// 기존
import { VideoPlayer } from '../components/VideoPlayer/MediaPlayer';
// 새로운
import MediaPlayerV2 from '../components/VideoPlayer/MediaPlayer.v2';
제거된 props:
jumpBy,initialJumpDelay,jumpDelayplaybackRateHashonFastForward,onRewind,onJumpBackward,onJumpForwardfeedbackHideDelay,miniFeedbackHideDelaynoMediaSliderFeedback,noMiniFeedback,noSlidertitle,infoComponents- 기타 PlayerPanel 전용 props
📝 Notes
Modal 전환 작동 방식
- MediaPanel이
getBoundingClientRect()로 스타일 계산 - MediaPlayerV2는 받은
style을 그대로 적용 modal플래그에 따라 controls/spotlight 활성화 제어
→ MediaPlayerV2는 전환 로직 구현 불필요
webOS 호환성
window.PalmSystem존재 시Media컴포넌트 사용- 브라우저에서는
TReactPlayer사용 - YouTube URL은 항상
TReactPlayer사용
🐛 알려진 제약사항
- Seek bar 없음: 단순 재생만 지원
- 빠르기 조정 없음: 배속 재생 미지원
- 간단한 Controls: 재생/일시정지 버튼만
복잡한 컨트롤이 필요하다면 기존 MediaPlayer.jsx 사용을 권장합니다.