🕐 커밋 시간: 2025. 11. 25. 10:41:26 📊 변경 통계: • 총 파일: 3개 • 추가: +101줄 • 삭제: -76줄 📝 수정된 파일: ~ com.twin.app.shoptime/src/components/VideoPlayer/TReactPlayer.jsx ~ com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.js ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx 🔧 주요 변경 내용: • UI 컴포넌트 아키텍처 개선 • 중간 규모 기능 개선 • 코드 정리 및 최적화
173 lines
5.3 KiB
JavaScript
173 lines
5.3 KiB
JavaScript
import React, { useCallback, useMemo, useRef, useEffect } from 'react';
|
|
|
|
import ReactPlayer from 'react-player';
|
|
|
|
import handle from '@enact/core/handle';
|
|
|
|
var handledMediaEventsMap = [
|
|
'onReady',
|
|
'onStart',
|
|
'onPlay',
|
|
'onProgress',
|
|
'onDuration',
|
|
'onPause',
|
|
'onBuffer',
|
|
'onBufferEnd',
|
|
'onSeek',
|
|
'onEnded',
|
|
'onError',
|
|
];
|
|
|
|
export default function TReactPlayer({
|
|
mediaEventsMap = handledMediaEventsMap,
|
|
videoRef,
|
|
url,
|
|
dispatch,
|
|
...rest
|
|
}) {
|
|
const playerRef = useRef(null);
|
|
|
|
// 🔽 [최적화] handleEvent의 주요 의존성 추출
|
|
const playing = rest?.playing;
|
|
const config = rest?.config;
|
|
|
|
const handleEvent = useCallback(
|
|
(type) => (ev) => {
|
|
if (type === 'onReady') {
|
|
if (videoRef) {
|
|
const videoNode = playerRef.current.getInternalPlayer();
|
|
videoRef(videoNode);
|
|
const iframeEl =
|
|
typeof playerRef.current?.getInternalPlayer === 'function'
|
|
? playerRef.current.getInternalPlayer('iframe')
|
|
: null;
|
|
if (iframeEl) {
|
|
iframeEl.setAttribute('tabIndex', '-1');
|
|
iframeEl.setAttribute('aria-hidden', 'true');
|
|
}
|
|
if (
|
|
videoNode.tagName &&
|
|
!Object.prototype.hasOwnProperty.call(videoNode, 'proportionPlayed')
|
|
) {
|
|
Object.defineProperties(videoNode, {
|
|
error: {
|
|
get: function () {
|
|
return videoNode.networkState === videoNode.NETWORK_NO_SOURCE;
|
|
},
|
|
},
|
|
loading: {
|
|
get: function () {
|
|
return videoNode.readyState < videoNode.HAVE_ENOUGH_DATA;
|
|
},
|
|
},
|
|
proportionLoaded: {
|
|
get: function () {
|
|
return (
|
|
videoNode.buffered.length &&
|
|
videoNode.buffered.end(videoNode.buffered.length - 1) / videoNode.duration
|
|
);
|
|
},
|
|
},
|
|
proportionPlayed: {
|
|
get: function () {
|
|
return videoNode.currentTime / videoNode.duration;
|
|
},
|
|
},
|
|
});
|
|
} else if (!Object.prototype.hasOwnProperty.call(videoNode, 'proportionPlayed')) {
|
|
videoNode.play = videoNode.playVideo;
|
|
videoNode.pause = videoNode.pauseVideo;
|
|
videoNode.seek = videoNode.seekTo;
|
|
Object.defineProperties(videoNode, {
|
|
currentTime: {
|
|
get: function () {
|
|
return videoNode.getCurrentTime();
|
|
},
|
|
set: function (time) {
|
|
videoNode.seekTo(time);
|
|
},
|
|
},
|
|
duration: {
|
|
get: function () {
|
|
return videoNode.getDuration();
|
|
},
|
|
},
|
|
paused: {
|
|
get: function () {
|
|
return videoNode.getPlayerState() !== 1;
|
|
},
|
|
},
|
|
error: {
|
|
get: function () {
|
|
return !!videoNode?.playerInfo?.videoData?.errorCode;
|
|
},
|
|
},
|
|
loading: {
|
|
get: function () {
|
|
return !videoNode?.playerInfo?.videoData?.isPlayable; //todo
|
|
},
|
|
},
|
|
proportionLoaded: {
|
|
get: function () {
|
|
return videoNode?.getVideoBytesLoaded() ?? 0;
|
|
},
|
|
},
|
|
proportionPlayed: {
|
|
get: function () {
|
|
const duration = videoNode.getDuration();
|
|
return duration ? videoNode.getCurrentTime() / duration : 0;
|
|
},
|
|
},
|
|
playbackRate: {
|
|
get: function () {
|
|
return videoNode?.playerInfo?.playbackRate;
|
|
},
|
|
set: function (playbackRate) {
|
|
if (videoNode && videoNode.setPlaybackRate) {
|
|
videoNode.setPlaybackRate(playbackRate);
|
|
}
|
|
},
|
|
},
|
|
});
|
|
}
|
|
}
|
|
handle.forward('onLoadStart', { type, ev }, rest);
|
|
}
|
|
if (type === 'onEnded' && rest?.isYoutube && rest?.type === 'VOD') {
|
|
// YouTube 재생 종료 시 iframe이 포커스를 가져가는 문제를 방지
|
|
const iframeEl =
|
|
typeof playerRef.current?.getInternalPlayer === 'function'
|
|
? playerRef.current.getInternalPlayer('iframe')
|
|
: null;
|
|
iframeEl?.blur();
|
|
}
|
|
handle.forward('onUpdate', { type, ev }, rest);
|
|
},
|
|
[videoRef, playing, config] // ✅ 주요 의존성 추가
|
|
);
|
|
|
|
const handledMediaEvents = useMemo(() => {
|
|
const events = {};
|
|
for (let i = 0; i < mediaEventsMap.length; i++) {
|
|
const eventName = mediaEventsMap[i];
|
|
events[eventName] = handleEvent(eventName);
|
|
}
|
|
return events;
|
|
}, [handleEvent, mediaEventsMap]);
|
|
|
|
// 메모리 정리는 VideoPlayer.js componentWillUnmount에서 수행
|
|
// TReactPlayer는 react-player 래퍼 역할만 수행
|
|
|
|
return (
|
|
<ReactPlayer
|
|
ref={playerRef}
|
|
url={url}
|
|
progressInterval={1000}
|
|
config={rest.config}
|
|
{...handledMediaEvents}
|
|
{...rest}
|
|
playsinline // Add playsinline attribute here
|
|
/>
|
|
);
|
|
}
|