docs: Add MediaPlayer.v2 required changes analysis
MediaPanel 실제 사용 컨텍스트 분석 및 필수 수정 사항 문서화 주요 발견사항: - DetailPanel → ProductVideo → MediaPanel 사용 패턴 분석 - Modal 모드: 오버레이 없음 (정상) - Fullscreen 모드: MediaSlider 필수 (누락됨) 필수 수정 사항: - MediaSlider 추가 (리모컨으로 진행 위치 조정) - Times 컴포넌트 추가 (현재/전체 시간 표시) - proportionLoaded/Played 상태 추가 - Slider 이벤트 핸들러 구현 조건부 렌더링: - Modal 모드 (modal=true): Slider 숨김 - Fullscreen 모드 (modal=false): Slider 표시 예상 작업 시간: 1.5시간
This commit is contained in:
404
.docs/MediaPlayer-v2-Required-Changes.md
Normal file
404
.docs/MediaPlayer-v2-Required-Changes.md
Normal file
@@ -0,0 +1,404 @@
|
|||||||
|
# MediaPlayer.v2 필수 수정 사항
|
||||||
|
|
||||||
|
**작성일**: 2025-11-10
|
||||||
|
**발견 사항**: MediaPanel의 실제 사용 컨텍스트 분석
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 실제 사용 패턴 분석
|
||||||
|
|
||||||
|
### 사용 위치
|
||||||
|
```
|
||||||
|
DetailPanel
|
||||||
|
→ ProductAllSection
|
||||||
|
→ ProductVideo
|
||||||
|
→ startMediaPlayer()
|
||||||
|
→ MediaPanel
|
||||||
|
→ MediaPlayer (VideoPlayer)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 동작 플로우
|
||||||
|
|
||||||
|
#### 1️⃣ **Modal 모드 시작** (작은 화면)
|
||||||
|
```javascript
|
||||||
|
// 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 모드 전환** (최대화면)
|
||||||
|
```javascript
|
||||||
|
// ProductVideo.jsx:164-168
|
||||||
|
if (isCurrentlyPlayingModal) {
|
||||||
|
dispatch(switchMediaToFullscreen()); // modal: false로 변경
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fullscreen 모드 특징**:
|
||||||
|
- 전체 화면 표시
|
||||||
|
- **리모컨 엔터 키 → 오버레이 표시 필수**
|
||||||
|
- ✅ Back 버튼
|
||||||
|
- ✅ **비디오 진행 바 (MediaSlider)** ← 필수!
|
||||||
|
- ✅ 현재 시간 / 전체 시간 (Times)
|
||||||
|
- ✅ Play/Pause 버튼 (MediaControls)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚨 현재 MediaPlayer.v2의 문제점
|
||||||
|
|
||||||
|
### ❌ 제거된 필수 기능
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 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 조건부 렌더링
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 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>
|
||||||
|
)}
|
||||||
|
```
|
||||||
|
|
||||||
|
**핵심 조건**:
|
||||||
|
```javascript
|
||||||
|
!panelInfo.modal // Modal이 아닐 때만 MediaSlider 표시
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 MediaPlayer.v2 수정 필요 사항
|
||||||
|
|
||||||
|
### 1. 상태 추가
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 현재 (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 추가
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { MediaSlider, Times, secondsToTime } from '../MediaPlayer';
|
||||||
|
import DurationFmt from 'ilib/lib/DurationFmt';
|
||||||
|
import { memoize } from '@enact/core/util';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. DurationFmt 헬퍼 추가
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
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 계산)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
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 이벤트 핸들러 추가
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
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 수정
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{/* 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 추가
|
||||||
|
|
||||||
|
```less
|
||||||
|
// 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 (권장)
|
||||||
|
4. Slider 이벤트 핸들러 세부 구현
|
||||||
|
5. Spotlight 키 네비게이션 (위/아래로 slider ↔ buttons)
|
||||||
|
6. CSS 스타일 개선
|
||||||
|
|
||||||
|
### Low Priority (선택)
|
||||||
|
7. Scrub 시 썸네일 표시 (기존에도 없음)
|
||||||
|
8. 추가 피드백 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 모드에서만 조건부 숨김**
|
||||||
Reference in New Issue
Block a user