From eaa0201469d3c27339626d5b30d75abf41d92f6a Mon Sep 17 00:00:00 2001 From: optrader Date: Sat, 22 Nov 2025 05:24:48 +0900 Subject: [PATCH] =?UTF-8?q?[251122]=20fix:=20Youtube=20=EB=B9=84=EB=94=94?= =?UTF-8?q?=EC=98=A4=20=EB=B0=98=EB=B3=B5=EC=9E=AC=EC=83=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ๐Ÿ• ์ปค๋ฐ‹ ์‹œ๊ฐ„: 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 ์ปดํฌ๋„ŒํŠธ ์•„ํ‚คํ…์ฒ˜ ๊ฐœ์„  โ€ข ์†Œ๊ทœ๋ชจ ๊ธฐ๋Šฅ ๊ฐœ์„  --- .../components/VideoPlayer/TReactPlayer.jsx | 8 ++ .../src/views/PlayerPanel/PlayerPanel.jsx | 109 +++++++++++++----- 2 files changed, 88 insertions(+), 29 deletions(-) diff --git a/com.twin.app.shoptime/src/components/VideoPlayer/TReactPlayer.jsx b/com.twin.app.shoptime/src/components/VideoPlayer/TReactPlayer.jsx index 15392dcc..9ec8371f 100644 --- a/com.twin.app.shoptime/src/components/VideoPlayer/TReactPlayer.jsx +++ b/com.twin.app.shoptime/src/components/VideoPlayer/TReactPlayer.jsx @@ -133,6 +133,14 @@ export default function TReactPlayer({ } 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] // โœ… ์ฃผ์š” ์˜์กด์„ฑ ์ถ”๊ฐ€ diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx index 42809934..6c7398a8 100644 --- a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx +++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx @@ -169,6 +169,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props }); const videoPlayer = useRef(null); + const focusReturnRef = useRef(null); const modalPrevRef = useRef(panelInfo?.modal); const prevIsTopPanelDetailFromPlayerRef = useRef(false); const [playListInfo, setPlayListInfo] = USE_STATE('playListInfo', ''); @@ -1959,40 +1960,84 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props }; }, []); - const onEnded = useCallback((e) => { - if (panelInfoRef.current.shptmBanrTpNm === 'MEDIA') { - dispatch( - updatePanel({ - name: panel_names.DETAIL_PANEL, - panelInfo: { - launchedFromPlayer: true, - isPlayerFinished: true, - }, - }) - ); - Spotlight.pause(); - setTimeout(() => { - Spotlight.resume(); - dispatch(PanelActions.popPanel()); - }, VIDEO_END_ACTION_DELAY); + 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; } - if (panelInfoRef.current.shptmBanrTpNm === 'VOD') { - Spotlight.pause(); - setTimeout(() => { - Spotlight.resume(); - if (panelInfoRef.current.modal) { - videoPlayer.current.play(); - } else { - dispatch(PanelActions.popPanel(panel_names.PLAYER_PANEL)); - } - }, VIDEO_END_ACTION_DELAY); - e?.stopPropagation(); - e?.preventDefault(); - 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') { + dispatch( + updatePanel({ + name: panel_names.DETAIL_PANEL, + panelInfo: { + launchedFromPlayer: true, + isPlayerFinished: true, + }, + }) + ); + Spotlight.pause(); + setTimeout(() => { + Spotlight.resume(); + dispatch(PanelActions.popPanel()); + }, VIDEO_END_ACTION_DELAY); + return; + } + if (panelInfoRef.current.shptmBanrTpNm === 'VOD') { + Spotlight.pause(); + setTimeout(() => { + stopExternalPlayer(); + if (panelInfoRef.current.modal) { + // ๋ชจ๋‹ฌ ๋ชจ๋“œ์—์„œ๋Š” ์ข…๋ฃŒ ํ›„ ํ™”๋ฉด์„ ์œ ์ง€ํ•˜๊ณ  Back ์•„์ด์ฝ˜์œผ๋กœ ํฌ์ปค์Šค ์ด๋™ + videoPlayer.current?.showControls?.(); + } else { + // ์ „์ฒดํ™”๋ฉด ๋ชจ๋“œ์—์„œ๋„ ์ข…๋ฃŒ ํ›„ ์ฆ‰์‹œ ๋‹ซ์ง€ ์•Š๊ณ  ์ œ์–ด๊ถŒ ํšŒ์ˆ˜ + videoPlayer.current?.showControls?.(); + } + Spotlight.resume(); + focusBackButtonOrFallback(); + }, VIDEO_END_ACTION_DELAY); + e?.stopPropagation(); + e?.preventDefault(); + return; + } + }, + [dispatch, focusBackButtonOrFallback, stopExternalPlayer] + ); + const onKeyDown = (ev) => { if (ev.keyCode === 34) { handleIndicatorDownClick(); @@ -2512,6 +2557,12 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props // } // }} > +