Files
shoptime/com.twin.app.shoptime/.docs/MediaPlayer-v2-README.md
optrader fd5a171a28 restore: .docs 폴더 복원 및 .gitignore 수정
- 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>
2025-11-11 10:00:59 +09: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 사용을 권장합니다.


📚 관련 문서