Files
shoptime/com.twin.app.shoptime/.docs/MediaPlayer-v2-Required-Changes.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.7 KiB
Raw Blame History

MediaPlayer.v2 필수 수정 사항

작성일: 2025-11-10 발견 사항: MediaPanel의 실제 사용 컨텍스트 분석


🔍 실제 사용 패턴 분석

사용 위치

DetailPanel
  → ProductAllSection
    → ProductVideo
      → startMediaPlayer()
        → MediaPanel
          → MediaPlayer (VideoPlayer)

동작 플로우

1 Modal 모드 시작 (작은 화면)

// ProductVideo.jsx:174-198
dispatch(startMediaPlayer({
  modal: true,                           // 작은 화면 모드
  modalContainerId: 'product-video-player',
  showUrl: productInfo.prdtMediaUrl,
  thumbnailUrl: productInfo.thumbnailUrl960,
  // ...
}));

Modal 모드 특징:

  • 화면 일부 영역에 fixed position으로 표시
  • 오버레이 없음 (controls, slider 모두 숨김)
  • 클릭만 가능 (전체화면으로 전환)

2 Fullscreen 모드 전환 (최대화면)

// ProductVideo.jsx:164-168
if (isCurrentlyPlayingModal) {
  dispatch(switchMediaToFullscreen());  // modal: false로 변경
}

Fullscreen 모드 특징:

  • 전체 화면 표시
  • 리모컨 엔터 키 → 오버레이 표시 필수
    • Back 버튼
    • 비디오 진행 바 (MediaSlider) ← 필수!
    • 현재 시간 / 전체 시간 (Times)
    • Play/Pause 버튼 (MediaControls)

🚨 현재 MediaPlayer.v2의 문제점

제거된 필수 기능

// MediaPlayer.v2.jsx - 현재 상태
{controlsVisible && !isModal && (
  <div className={css.simpleControls}>
    <button onClick={...}>{paused ? '▶' : '⏸'}</button>  // Play/Pause만
    <button onClick={onBackButton}> Back</button>
  </div>
)}

문제:

  1. MediaSlider (seek bar) 없음 - 리모컨으로 진행 위치 조정 불가
  2. Times 컴포넌트 없음 - 현재 시간/전체 시간 표시 안 됨
  3. proportionLoaded, proportionPlayed 상태 없음

기존 MediaPlayer.jsx의 올바른 구현

Modal vs Fullscreen 조건부 렌더링

// MediaPlayer.jsx:2415-2461
{noSlider ? null : (
  <div className={css.sliderContainer}>
    {/* Times - 전체 시간 */}
    {this.state.mediaSliderVisible && type ? (
      <Times
        noCurrentTime
        total={this.state.duration}
        formatter={durFmt}
        type={type}
      />
    ) : null}

    {/* Times - 현재 시간 */}
    {this.state.mediaSliderVisible && type ? (
      <Times
        noTotalTime
        current={this.state.currentTime}
        formatter={durFmt}
      />
    ) : null}

    {/* MediaSlider - modal이 아닐 때만 표시 */}
    {!panelInfo.modal && (
      <MediaSlider
        backgroundProgress={this.state.proportionLoaded}
        disabled={disabled || this.state.sourceUnavailable}
        value={this.state.proportionPlayed}
        visible={this.state.mediaSliderVisible}
        spotlightDisabled={
          spotlightDisabled || !this.state.mediaControlsVisible
        }
        onChange={this.onSliderChange}
        onKnobMove={this.handleKnobMove}
        onKeyDown={this.handleSliderKeyDown}
        // ...
      />
    )}
  </div>
)}

핵심 조건:

!panelInfo.modal  // Modal이 아닐 때만 MediaSlider 표시

📋 MediaPlayer.v2 수정 필요 사항

1. 상태 추가

// 현재 (7개)
const [currentTime, setCurrentTime] = useState(0);
const [duration, setDuration] = useState(0);
const [paused, setPaused] = useState(!autoPlay);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [controlsVisible, setControlsVisible] = useState(false);
const [sourceUnavailable, setSourceUnavailable] = useState(true);

// 추가 필요 (2개)
const [proportionLoaded, setProportionLoaded] = useState(0);     // 로딩된 비율
const [proportionPlayed, setProportionPlayed] = useState(0);     // 재생된 비율

2. Import 추가

import { MediaSlider, Times, secondsToTime } from '../MediaPlayer';
import DurationFmt from 'ilib/lib/DurationFmt';
import { memoize } from '@enact/core/util';

3. DurationFmt 헬퍼 추가

const memoGetDurFmt = memoize(
  () => new DurationFmt({
    length: 'medium',
    style: 'clock',
    useNative: false,
  })
);

const getDurFmt = () => {
  if (typeof window === 'undefined') return null;
  return memoGetDurFmt();
};

4. handleUpdate 수정 (proportionLoaded/Played 계산)

const handleUpdate = useCallback((ev) => {
  const el = videoRef.current;
  if (!el) return;

  const newCurrentTime = el.currentTime || 0;
  const newDuration = el.duration || 0;

  setCurrentTime(newCurrentTime);
  setDuration(newDuration);
  setPaused(el.paused);
  setLoading(el.loading || false);
  setError(el.error || null);
  setSourceUnavailable((el.loading && sourceUnavailable) || el.error);

  // 추가: proportion 계산
  setProportionLoaded(el.proportionLoaded || 0);
  setProportionPlayed(newDuration > 0 ? newCurrentTime / newDuration : 0);

  // 콜백 호출
  if (ev.type === 'timeupdate' && onTimeUpdate) {
    onTimeUpdate(ev);
  }
  // ...
}, [onTimeUpdate, sourceUnavailable]);

5. Slider 이벤트 핸들러 추가

const handleSliderChange = useCallback(({ value }) => {
  const time = value * duration;
  seek(time);
}, [duration, seek]);

const handleKnobMove = useCallback((ev) => {
  if (!videoRef.current) return;

  const seconds = Math.floor(ev.proportion * videoRef.current.duration);
  if (!isNaN(seconds)) {
    // 스크럽 시 시간 표시 업데이트 등
    // 필요시 onScrub 콜백 호출
  }
}, []);

const handleSliderKeyDown = useCallback((ev) => {
  // Spotlight 키 이벤트 처리
  // 위/아래 키로 controls 이동 등
}, []);

6. Controls UI 수정

{/* Modal이 아닐 때만 전체 controls 표시 */}
{controlsVisible && !isModal && (
  <div className={css.controlsContainer}>
    {/* Slider Section */}
    <div className={css.sliderContainer}>
      {/* Times - 전체 시간 */}
      <Times
        noCurrentTime
        total={duration}
        formatter={getDurFmt()}
        type={type}
      />

      {/* Times - 현재 시간 */}
      <Times
        noTotalTime
        current={currentTime}
        formatter={getDurFmt()}
      />

      {/* MediaSlider */}
      <MediaSlider
        backgroundProgress={proportionLoaded}
        disabled={disabled || sourceUnavailable}
        value={proportionPlayed}
        visible={controlsVisible}
        spotlightDisabled={spotlightDisabled}
        onChange={handleSliderChange}
        onKnobMove={handleKnobMove}
        onKeyDown={handleSliderKeyDown}
        spotlightId="media-slider-v2"
      />
    </div>

    {/* Controls Section */}
    <div className={css.controlsButtons}>
      <button className={css.playPauseBtn} onClick={...}>
        {paused ? '▶' : '⏸'}
      </button>

      {onBackButton && (
        <button className={css.backBtn} onClick={onBackButton}>
           Back
        </button>
      )}
    </div>
  </div>
)}

7. CSS 추가

// VideoPlayer.module.less

.controlsContainer {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  padding: 20px;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.8), transparent);
  z-index: 10;
}

.sliderContainer {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 12px;
}

.controlsButtons {
  display: flex;
  gap: 20px;
  justify-content: center;
}

📊 수정 전/후 비교

현재 MediaPlayer.v2 (문제)

Modal 모드 (modal=true):
  ✅ 오버레이 없음 (정상)
  ✅ 클릭으로 전환 (정상)

Fullscreen 모드 (modal=false):
  ❌ MediaSlider 없음 (문제!)
  ❌ Times 없음 (문제!)
  ✅ Play/Pause 버튼 (정상)
  ✅ Back 버튼 (정상)

수정 후 MediaPlayer.v2 (정상)

Modal 모드 (modal=true):
  ✅ 오버레이 없음
  ✅ 클릭으로 전환

Fullscreen 모드 (modal=false):
  ✅ MediaSlider (seek bar)
  ✅ Times (현재/전체 시간)
  ✅ Play/Pause 버튼
  ✅ Back 버튼

🎯 우선순위

High Priority (필수)

  1. MediaSlider 추가 - 리모컨으로 진행 위치 조정
  2. Times 컴포넌트 추가 - 시간 표시
  3. proportionLoaded/Played 상태 - slider 동작

Medium Priority (권장)

  1. Slider 이벤트 핸들러 세부 구현
  2. Spotlight 키 네비게이션 (위/아래로 slider ↔ buttons)
  3. CSS 스타일 개선

Low Priority (선택)

  1. Scrub 시 썸네일 표시 (기존에도 없음)
  2. 추가 피드백 UI

🔧 구현 순서

  1. Phase 1: 상태 및 import 추가 (10분)
  2. Phase 2: MediaSlider 렌더링 (20분)
  3. Phase 3: Times 컴포넌트 추가 (10분)
  4. Phase 4: 이벤트 핸들러 구현 (20분)
  5. Phase 5: CSS 스타일 조정 (10분)
  6. Phase 6: 테스트 및 디버깅 (30분)

총 예상 시간: 약 1.5시간


체크리스트

  • proportionLoaded, proportionPlayed 상태 추가
  • MediaSlider, Times import
  • DurationFmt 헬퍼 추가
  • handleUpdate에서 proportion 계산
  • handleSliderChange 구현
  • handleKnobMove 구현
  • handleSliderKeyDown 구현
  • Controls UI에 slider 추가
  • Times 컴포넌트 추가
  • CSS 스타일 추가
  • Modal 모드에서 slider 숨김 확인
  • Fullscreen 모드에서 slider 표시 확인
  • 리모컨으로 seek 동작 테스트

📝 결론

MediaPlayer.v2는 MediaSlider와 Times가 필수입니다.

이유:

  1. DetailPanel → ProductVideo에서만 사용
  2. Fullscreen 모드에서 리모컨 사용자가 비디오 진행 위치를 조정해야 함
  3. 현재/전체 시간 표시 필요

→ "간소화"는 맞지만, "필수 기능 제거"는 아님 → MediaSlider는 제거 불가, 단 Modal 모드에서만 조건부 숨김