From eed4ef890942dfe6094279d4e82c7588c3a9d91e Mon Sep 17 00:00:00 2001 From: optrader Date: Mon, 24 Nov 2025 17:55:08 +0900 Subject: [PATCH] =?UTF-8?q?[251124]=20fix:=20PlayerPanel,VideoPlayer=20?= =?UTF-8?q?=EC=B5=9C=EC=A0=81=ED=99=94-3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit πŸ• 컀밋 μ‹œκ°„: 2025. 11. 24. 17:55:07 πŸ“Š λ³€κ²½ 톡계: β€’ 총 파일: 5개 β€’ μΆ”κ°€: +66쀄 β€’ μ‚­μ œ: -1쀄 πŸ“ μˆ˜μ •λœ 파일: ~ com.twin.app.shoptime/src/actions/actionTypes.js ~ com.twin.app.shoptime/src/actions/playActions.js ~ com.twin.app.shoptime/src/components/VideoPlayer/TReactPlayer.jsx ~ com.twin.app.shoptime/src/reducers/playReducer.js ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx πŸ”§ μ£Όμš” λ³€κ²½ λ‚΄μš©: β€’ νƒ€μž… μ‹œμŠ€ν…œ μ•ˆμ •μ„± κ°•ν™” β€’ 핡심 λΉ„μ¦ˆλ‹ˆμŠ€ 둜직 κ°œμ„  β€’ UI μ»΄ν¬λ„ŒνŠΈ μ•„ν‚€ν…μ²˜ κ°œμ„  β€’ μ†Œκ·œλͺ¨ κΈ°λŠ₯ κ°œμ„  --- .../src/actions/actionTypes.js | 1 + .../src/actions/playActions.js | 22 +++++++++++++++ .../components/VideoPlayer/TReactPlayer.jsx | 5 ++++ .../src/reducers/playReducer.js | 12 +++++++++ .../src/views/PlayerPanel/PlayerPanel.jsx | 27 ++++++++++++++++++- 5 files changed, 66 insertions(+), 1 deletion(-) diff --git a/com.twin.app.shoptime/src/actions/actionTypes.js b/com.twin.app.shoptime/src/actions/actionTypes.js index 0507bedb..a62e7509 100644 --- a/com.twin.app.shoptime/src/actions/actionTypes.js +++ b/com.twin.app.shoptime/src/actions/actionTypes.js @@ -257,6 +257,7 @@ export const types = { GET_CHAT_LOG: 'GET_CHAT_LOG', GET_SUBTITLE: 'GET_SUBTITLE', CLEAR_PLAYER_INFO: 'CLEAR_PLAYER_INFO', + CLEAR_SUBTITLE_BLOB: 'CLEAR_SUBTITLE_BLOB', UPDATE_VIDEO_PLAY_STATE: 'UPDATE_VIDEO_PLAY_STATE', // πŸ”½ [251116] μƒˆλ‘œμš΄ λΉ„λ””μ˜€ μƒνƒœ 관리 μ‹œμŠ€ν…œ - μž¬μƒ μƒνƒœ diff --git a/com.twin.app.shoptime/src/actions/playActions.js b/com.twin.app.shoptime/src/actions/playActions.js index 594bf9fc..2db5ac53 100644 --- a/com.twin.app.shoptime/src/actions/playActions.js +++ b/com.twin.app.shoptime/src/actions/playActions.js @@ -786,6 +786,28 @@ export const CLEAR_PLAYER_INFO = () => ({ type: types.CLEAR_PLAYER_INFO, }); +// νŠΉμ • μžλ§‰ Blob URL을 ν•΄μ œν•˜λŠ” μ•‘μ…˜ μƒμ„±μž +export const clearSubtitleBlob = (subtitleUrl) => (dispatch, getState) => { + const currentBlobs = getState().play.subTitleBlobs; + const blobUrl = currentBlobs[subtitleUrl]; + + // Blob URL ν•΄μ œ + if (blobUrl && blobUrl.startsWith('blob:')) { + try { + URL.revokeObjectURL(blobUrl); + dlog('[clearSubtitleBlob] Revoked Blob URL:', subtitleUrl); + } catch (error) { + derror('[clearSubtitleBlob] Failed to revoke Blob URL:', error); + } + } + + // Redux μƒνƒœμ—μ„œ 제거 + dispatch({ + type: types.CLEAR_SUBTITLE_BLOB, + payload: { subtitleUrl } + }); +}; + /** * λΉ„λ””μ˜€ μž¬μƒ μƒνƒœλ₯Ό Redux에 μ—…λ°μ΄νŠΈν•©λ‹ˆλ‹€. * @param {Object} playState - μ—…λ°μ΄νŠΈν•  μž¬μƒ μƒνƒœ diff --git a/com.twin.app.shoptime/src/components/VideoPlayer/TReactPlayer.jsx b/com.twin.app.shoptime/src/components/VideoPlayer/TReactPlayer.jsx index 314ff62b..eac230ac 100644 --- a/com.twin.app.shoptime/src/components/VideoPlayer/TReactPlayer.jsx +++ b/com.twin.app.shoptime/src/components/VideoPlayer/TReactPlayer.jsx @@ -169,6 +169,11 @@ export default function TReactPlayer({ if (typeof videoNode.stopVideo === 'function' && videoNode.stopVideo !== videoNode.pause) { videoNode.stopVideo(); } + // HLS μΈμŠ€ν„΄μŠ€κ°€ μ‘΄μž¬ν•˜λ©΄ λͺ…μ‹œμ μœΌλ‘œ 파괴 + const hls = playerRef.current?.getInternalPlayer?.('hls'); + if (hls && typeof hls.destroy === 'function') { + hls.destroy(); + } } catch (err) { console.warn('[TReactPlayer] cleanup warning:', err); } diff --git a/com.twin.app.shoptime/src/reducers/playReducer.js b/com.twin.app.shoptime/src/reducers/playReducer.js index 78389d86..8827d3d1 100644 --- a/com.twin.app.shoptime/src/reducers/playReducer.js +++ b/com.twin.app.shoptime/src/reducers/playReducer.js @@ -103,6 +103,18 @@ export const playReducer = (state = initialState, action) => { subTitleBlobs: {}, }; } + case types.CLEAR_SUBTITLE_BLOB: { + const { subtitleUrl } = action.payload; + const newSubTitleBlobs = { ...state.subTitleBlobs }; + + // νŠΉμ • URL만 제거 + delete newSubTitleBlobs[subtitleUrl]; + + return { + ...state, + subTitleBlobs: newSubTitleBlobs, + }; + } case types.UPDATE_VIDEO_PLAY_STATE: { const newState = { ...state.videoPlayState, diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx index 2676751f..4e179bab 100644 --- a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx +++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx @@ -34,6 +34,7 @@ import * as PanelActions from '../../actions/panelActions'; import { updatePanel } from '../../actions/panelActions'; import { CLEAR_PLAYER_INFO, + clearSubtitleBlob, getChatLog, getSubTitle, startVideoPlayer, @@ -261,6 +262,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props const liveShowInfos = USE_SELECTOR('liveShowInfos', (state) => state.main.liveShowInfos); const vodSubtitleData = USE_SELECTOR('vodSubtitleData', (state) => state.play.subTitleBlobs); const previousSubtitleBlobRef = useRef(null); + const previousSubtitleUrlRef = useRef(null); const broadcast = USE_SELECTOR('broadcast', (state) => state.common.broadcast); const videoPlayState = USE_SELECTOR('videoPlayState', (state) => state.play.videoPlayState); @@ -1493,6 +1495,13 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props } }, [panelInfo?.shptmBanrTpNm, playListInfo]); + // μ»΄ν¬λ„ŒνŠΈ μ–Έλ§ˆμš΄νŠΈ μ‹œ Job 정리 + useEffect(() => { + return () => { + initialFocusTimeoutJob.current?.stop?.(); + }; + }, []); + // live subtitle Luna API useEffect(() => { if (currentSubtitleBlob) { @@ -1953,7 +1962,23 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props if (currentSubtitleUrl) { dispatch(getSubTitle({ showSubtitleUrl: currentSubtitleUrl })); } - }, [currentSubtitleUrl]); + + // 이전 μžλ§‰ URL 정리 (Redux λ©”λͺ¨λ¦¬ λˆ„μˆ˜ λ°©μ§€) + const prevSubtitleUrl = previousSubtitleUrlRef.current; + if (prevSubtitleUrl && prevSubtitleUrl !== currentSubtitleUrl) { + dispatch(clearSubtitleBlob(prevSubtitleUrl)); + dlog('[PlayerPanel] Clearing previous subtitle URL:', prevSubtitleUrl); + } + + previousSubtitleUrlRef.current = currentSubtitleUrl; + + // μ»΄ν¬λ„ŒνŠΈ μ–Έλ§ˆμš΄νŠΈ μ‹œ λ§ˆμ§€λ§‰ μžλ§‰ URL 정리 + return () => { + if (previousSubtitleUrlRef.current) { + dispatch(clearSubtitleBlob(previousSubtitleUrlRef.current)); + } + }; + }, [currentSubtitleUrl, dispatch]); useEffect(() => { setVideoLoaded(false);