[251122] fix: Youtube 비디오 반복재생

🕐 커밋 시간: 2025. 11. 22. 05:24:47

📊 변경 통계:
  • 총 파일: 2개
  • 추가: +60줄
  • 삭제: -4줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/components/VideoPlayer/TReactPlayer.jsx
  ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx

🔧 주요 변경 내용:
  • UI 컴포넌트 아키텍처 개선
  • 소규모 기능 개선
This commit is contained in:
2025-11-22 05:24:48 +09:00
parent cf2cc57d95
commit eaa0201469
2 changed files with 88 additions and 29 deletions

View File

@@ -133,6 +133,14 @@ export default function TReactPlayer({
} }
handle.forward('onLoadStart', { type, ev }, rest); 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); handle.forward('onUpdate', { type, ev }, rest);
}, },
[videoRef, playing, config] // ✅ 주요 의존성 추가 [videoRef, playing, config] // ✅ 주요 의존성 추가

View File

@@ -169,6 +169,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
}); });
const videoPlayer = useRef(null); const videoPlayer = useRef(null);
const focusReturnRef = useRef(null);
const modalPrevRef = useRef(panelInfo?.modal); const modalPrevRef = useRef(panelInfo?.modal);
const prevIsTopPanelDetailFromPlayerRef = useRef(false); const prevIsTopPanelDetailFromPlayerRef = useRef(false);
const [playListInfo, setPlayListInfo] = USE_STATE('playListInfo', ''); const [playListInfo, setPlayListInfo] = USE_STATE('playListInfo', '');
@@ -1959,7 +1960,45 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
}; };
}, []); }, []);
const onEnded = useCallback((e) => { const focusBackToPanel = useCallback(() => {
// 포커스를 PlayerPanel 쪽으로 강제 이동해 YouTube iframe이 포커스를 가져가는 것을 차단
if (focusReturnRef.current) {
focusReturnRef.current.focus();
return true;
}
return false;
}, []);
const focusBackButtonOrFallback = useCallback(() => {
// 종료 시 Back 아이콘으로 포커스를 이동시키되 실패하면 센티넬로 폴백
if (Spotlight.focus('player-back-button')) {
return true;
}
return focusBackToPanel();
}, [focusBackToPanel]);
const stopExternalPlayer = useCallback(() => {
const playerInstance = videoPlayer.current;
const media = playerInstance?.video;
if (!media) {
return;
}
// ReactPlayer의 YouTube 인스턴스 대응: 종료 직후 리플레이 오버레이가 뜨지 않도록 정지/초기화 시도
if (typeof media.stopVideo === 'function') {
media.stopVideo();
}
if (typeof media.seekTo === 'function') {
media.seekTo(0);
}
if (typeof media.pause === 'function') {
media.pause();
}
}, []);
const onEnded = useCallback(
(e) => {
if (panelInfoRef.current.shptmBanrTpNm === 'MEDIA') { if (panelInfoRef.current.shptmBanrTpNm === 'MEDIA') {
dispatch( dispatch(
updatePanel({ updatePanel({
@@ -1980,18 +2019,24 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
if (panelInfoRef.current.shptmBanrTpNm === 'VOD') { if (panelInfoRef.current.shptmBanrTpNm === 'VOD') {
Spotlight.pause(); Spotlight.pause();
setTimeout(() => { setTimeout(() => {
Spotlight.resume(); stopExternalPlayer();
if (panelInfoRef.current.modal) { if (panelInfoRef.current.modal) {
videoPlayer.current.play(); // 모달 모드에서는 종료 후 화면을 유지하고 Back 아이콘으로 포커스 이동
videoPlayer.current?.showControls?.();
} else { } else {
dispatch(PanelActions.popPanel(panel_names.PLAYER_PANEL)); // 전체화면 모드에서도 종료 후 즉시 닫지 않고 제어권 회수
videoPlayer.current?.showControls?.();
} }
Spotlight.resume();
focusBackButtonOrFallback();
}, VIDEO_END_ACTION_DELAY); }, VIDEO_END_ACTION_DELAY);
e?.stopPropagation(); e?.stopPropagation();
e?.preventDefault(); e?.preventDefault();
return; return;
} }
}, []); },
[dispatch, focusBackButtonOrFallback, stopExternalPlayer]
);
const onKeyDown = (ev) => { const onKeyDown = (ev) => {
if (ev.keyCode === 34) { if (ev.keyCode === 34) {
@@ -2512,6 +2557,12 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
// } // }
// }} // }}
> >
<div
ref={focusReturnRef}
tabIndex={-1}
aria-hidden="true"
style={{ width: 1, height: 1, opacity: 0, position: 'absolute' }}
/>
{isReadyToPlay && ( {isReadyToPlay && (
<VideoPlayer <VideoPlayer
key={currentPlayingUrl} key={currentPlayingUrl}