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 69331956..d5b6a42f 100644 --- a/com.twin.app.shoptime/src/components/VideoPlayer/MediaPlayer.v2.jsx +++ b/com.twin.app.shoptime/src/components/VideoPlayer/MediaPlayer.v2.jsx @@ -49,7 +49,19 @@ const memoGetDurFmt = memoize( const getDurFmt = () => { if (typeof window === 'undefined') return null; - return memoGetDurFmt(); + + try { + return memoGetDurFmt(); + } catch (error) { + console.error('[MediaPlayer.v2] DurationFmt creation failed:', error); + // Fallback: simple formatter using secondsToTime + return { + format: (time) => { + if (!time || !time.millisecond) return '00:00'; + return secondsToTime(Math.floor(time.millisecond / 1000)); + } + }; + } }; /** @@ -162,6 +174,33 @@ const MediaPlayerV2 = forwardRef((props, ref) => { } }, [onLoadStart]); + // proportionLoaded 플랫폼별 계산 + const updateProportionLoaded = useCallback(() => { + if (!videoRef.current) return; + + // webOS Media 컴포넌트: proportionLoaded 속성 사용 + if (ActualVideoComponent === Media) { + const loaded = videoRef.current.proportionLoaded || 0; + setProportionLoaded(loaded); + return; + } + + // TReactPlayer/HTMLVideoElement: buffered API 사용 + const video = videoRef.current; + if (video.buffered && video.buffered.length > 0 && video.duration) { + try { + const bufferedEnd = video.buffered.end(video.buffered.length - 1); + const loaded = bufferedEnd / video.duration; + setProportionLoaded(loaded); + } catch (error) { + // buffered.end() can throw if index is out of range + setProportionLoaded(0); + } + } else { + setProportionLoaded(0); + } + }, [ActualVideoComponent]); + const handleUpdate = useCallback((ev) => { const el = videoRef.current; if (!el) return; @@ -178,7 +217,7 @@ const MediaPlayerV2 = forwardRef((props, ref) => { setSourceUnavailable((el.loading && sourceUnavailable) || el.error); // Proportion 계산 - setProportionLoaded(el.proportionLoaded || 0); + updateProportionLoaded(); // 플랫폼별 계산 함수 호출 setProportionPlayed(newDuration > 0 ? newCurrentTime / newDuration : 0); // 콜백 호출 @@ -194,7 +233,7 @@ const MediaPlayerV2 = forwardRef((props, ref) => { if (ev.type === 'durationchange' && onDurationChange) { onDurationChange(ev); } - }, [onTimeUpdate, onLoadedData, onLoadedMetadata, onDurationChange, sourceUnavailable]); + }, [onTimeUpdate, onLoadedData, onLoadedMetadata, onDurationChange, sourceUnavailable, updateProportionLoaded]); const handleEnded = useCallback((e) => { if (onEnded) { @@ -256,12 +295,18 @@ const MediaPlayerV2 = forwardRef((props, ref) => { }, [sourceUnavailable]); const seek = useCallback((timeIndex) => { - if (videoRef.current && !isNaN(videoRef.current.duration)) { - videoRef.current.currentTime = Math.min( - Math.max(0, timeIndex), - videoRef.current.duration - ); + if (!videoRef.current) return; + + const video = videoRef.current; + const dur = video.duration; + + // duration 유효성 체크 강화 (0, NaN, Infinity 모두 체크) + if (isNaN(dur) || dur === 0 || dur === Infinity) { + console.warn('[MediaPlayer.v2] seek failed: invalid duration', dur); + return; } + + video.currentTime = Math.min(Math.max(0, timeIndex), dur); }, []); const getMediaState = useCallback(() => { @@ -335,6 +380,20 @@ const MediaPlayerV2 = forwardRef((props, ref) => { prevModalRef.current = isModal; }, [isModal, play, showControls]); + // ========== proportionLoaded 주기적 업데이트 ========== + // TReactPlayer의 경우 buffered가 계속 변경되므로 주기적 체크 필요 + useEffect(() => { + // 초기 한 번 실행 + updateProportionLoaded(); + + // 1초마다 업데이트 + const interval = setInterval(() => { + updateProportionLoaded(); + }, 1000); + + return () => clearInterval(interval); + }, [updateProportionLoaded]); + // ========== Cleanup ========== useEffect(() => { return () => {