Files
shoptime/.docs/MediaPlayer-v2-README.md
Claude 05e54583a5 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 문서
2025-11-10 08:08:59 +00:00

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, jumpDelay
  • playbackRateHash
  • onFastForward, onRewind, onJumpBackward, onJumpForward
  • feedbackHideDelay, miniFeedbackHideDelay
  • noMediaSliderFeedback, noMiniFeedback, noSlider
  • title, infoComponents
  • 기타 PlayerPanel 전용 props

📝 Notes

Modal 전환 작동 방식

  1. MediaPanelgetBoundingClientRect()로 스타일 계산
  2. MediaPlayerV2는 받은 style을 그대로 적용
  3. modal 플래그에 따라 controls/spotlight 활성화 제어

MediaPlayerV2는 전환 로직 구현 불필요

webOS 호환성

  • window.PalmSystem 존재 시 Media 컴포넌트 사용
  • 브라우저에서는 TReactPlayer 사용
  • YouTube URL은 항상 TReactPlayer 사용

🐛 알려진 제약사항

  1. Seek bar 없음: 단순 재생만 지원
  2. 빠르기 조정 없음: 배속 재생 미지원
  3. 간단한 Controls: 재생/일시정지 버튼만

복잡한 컨트롤이 필요하다면 기존 MediaPlayer.jsx 사용을 권장합니다.


📚 관련 문서