- 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>
9.7 KiB
9.7 KiB
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>
)}
문제:
- ❌ MediaSlider (seek bar) 없음 - 리모컨으로 진행 위치 조정 불가
- ❌ Times 컴포넌트 없음 - 현재 시간/전체 시간 표시 안 됨
- ❌ 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 (필수)
- ✅ MediaSlider 추가 - 리모컨으로 진행 위치 조정
- ✅ Times 컴포넌트 추가 - 시간 표시
- ✅ proportionLoaded/Played 상태 - slider 동작
Medium Priority (권장)
- Slider 이벤트 핸들러 세부 구현
- Spotlight 키 네비게이션 (위/아래로 slider ↔ buttons)
- CSS 스타일 개선
Low Priority (선택)
- Scrub 시 썸네일 표시 (기존에도 없음)
- 추가 피드백 UI
🔧 구현 순서
- Phase 1: 상태 및 import 추가 (10분)
- Phase 2: MediaSlider 렌더링 (20분)
- Phase 3: Times 컴포넌트 추가 (10분)
- Phase 4: 이벤트 핸들러 구현 (20분)
- Phase 5: CSS 스타일 조정 (10분)
- 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가 필수입니다.
이유:
- DetailPanel → ProductVideo에서만 사용
- Fullscreen 모드에서 리모컨 사용자가 비디오 진행 위치를 조정해야 함
- 현재/전체 시간 표시 필요
→ "간소화"는 맞지만, "필수 기능 제거"는 아님 → MediaSlider는 제거 불가, 단 Modal 모드에서만 조건부 숨김