diff --git a/com.twin.app.shoptime/src/components/VideoPlayer/MediaPlayer.v2.jsx b/com.twin.app.shoptime/src/components/VideoPlayer/MediaPlayer.v2.jsx index 32d749c2..69331956 100644 --- a/com.twin.app.shoptime/src/components/VideoPlayer/MediaPlayer.v2.jsx +++ b/com.twin.app.shoptime/src/components/VideoPlayer/MediaPlayer.v2.jsx @@ -11,15 +11,18 @@ import React, { useState, useRef, useEffect, useCallback, useMemo, forwardRef, useImperativeHandle } from 'react'; import classNames from 'classnames'; +import DurationFmt from 'ilib/lib/DurationFmt'; import PropTypes from 'prop-types'; import { platform } from '@enact/core/platform'; +import { memoize } from '@enact/core/util'; import Spotlight from '@enact/spotlight'; import { SpotlightContainerDecorator } from '@enact/spotlight/SpotlightContainerDecorator'; import { Spottable } from '@enact/spotlight/Spottable'; import Touchable from '@enact/ui/Touchable'; import Loader from '../Loader/Loader'; +import { MediaSlider, Times, secondsToTime } from '../MediaPlayer'; import Overlay from './Overlay'; import Media from './Media'; import TReactPlayer from './TReactPlayer'; @@ -35,6 +38,20 @@ const RootContainer = SpotlightContainerDecorator( 'div' ); +// DurationFmt memoization +const memoGetDurFmt = memoize( + () => new DurationFmt({ + length: 'medium', + style: 'clock', + useNative: false, + }) +); + +const getDurFmt = () => { + if (typeof window === 'undefined') return null; + return memoGetDurFmt(); +}; + /** * MediaPlayer.v2 컴포넌트 */ @@ -96,6 +113,8 @@ const MediaPlayerV2 = forwardRef((props, ref) => { const [error, setError] = useState(null); const [controlsVisible, setControlsVisible] = useState(false); const [sourceUnavailable, setSourceUnavailable] = useState(true); + const [proportionLoaded, setProportionLoaded] = useState(0); + const [proportionPlayed, setProportionPlayed] = useState(0); // ========== Refs ========== const videoRef = useRef(null); @@ -147,14 +166,21 @@ const MediaPlayerV2 = forwardRef((props, ref) => { const el = videoRef.current; if (!el) return; + const newCurrentTime = el.currentTime || 0; + const newDuration = el.duration || 0; + // 상태 업데이트 - setCurrentTime(el.currentTime || 0); - setDuration(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); @@ -247,8 +273,31 @@ const MediaPlayerV2 = forwardRef((props, ref) => { error, playbackRate: videoRef.current?.playbackRate || 1, proportionPlayed: duration > 0 ? currentTime / duration : 0, + proportionLoaded, }; - }, [currentTime, duration, paused, loading, error]); + }, [currentTime, duration, paused, loading, error, proportionLoaded]); + + // ========== Slider Event Handlers ========== + 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)) { + // Scrub 시 시간 표시 업데이트 + // 필요시 onScrub 콜백 호출 가능 + } + }, []); + + const handleSliderKeyDown = useCallback((ev) => { + // Spotlight 키 이벤트 처리 + // 위/아래 키로 controls 이동 등 + // 기본 동작은 MediaSlider 내부에서 처리 + }, []); // ========== Video Click Handler (Modal 전환) ========== const handleVideoClick = useCallback(() => { @@ -398,34 +447,69 @@ const MediaPlayerV2 = forwardRef((props, ref) => { > )} - {/* Simple Controls */} + {/* Controls with MediaSlider */} {controlsVisible && !isModal && ( -