[251124] fix: PlayerPanel,VideoPlayer 최적화-3

🕐 커밋 시간: 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 컴포넌트 아키텍처 개선
  • 소규모 기능 개선
This commit is contained in:
2025-11-24 17:55:08 +09:00
parent becf984efc
commit eed4ef8909
5 changed files with 66 additions and 1 deletions

View File

@@ -257,6 +257,7 @@ export const types = {
GET_CHAT_LOG: 'GET_CHAT_LOG', GET_CHAT_LOG: 'GET_CHAT_LOG',
GET_SUBTITLE: 'GET_SUBTITLE', GET_SUBTITLE: 'GET_SUBTITLE',
CLEAR_PLAYER_INFO: 'CLEAR_PLAYER_INFO', CLEAR_PLAYER_INFO: 'CLEAR_PLAYER_INFO',
CLEAR_SUBTITLE_BLOB: 'CLEAR_SUBTITLE_BLOB',
UPDATE_VIDEO_PLAY_STATE: 'UPDATE_VIDEO_PLAY_STATE', UPDATE_VIDEO_PLAY_STATE: 'UPDATE_VIDEO_PLAY_STATE',
// 🔽 [251116] 새로운 비디오 상태 관리 시스템 - 재생 상태 // 🔽 [251116] 새로운 비디오 상태 관리 시스템 - 재생 상태

View File

@@ -786,6 +786,28 @@ export const CLEAR_PLAYER_INFO = () => ({
type: types.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에 업데이트합니다. * 비디오 재생 상태를 Redux에 업데이트합니다.
* @param {Object} playState - 업데이트할 재생 상태 * @param {Object} playState - 업데이트할 재생 상태

View File

@@ -169,6 +169,11 @@ export default function TReactPlayer({
if (typeof videoNode.stopVideo === 'function' && videoNode.stopVideo !== videoNode.pause) { if (typeof videoNode.stopVideo === 'function' && videoNode.stopVideo !== videoNode.pause) {
videoNode.stopVideo(); videoNode.stopVideo();
} }
// HLS 인스턴스가 존재하면 명시적으로 파괴
const hls = playerRef.current?.getInternalPlayer?.('hls');
if (hls && typeof hls.destroy === 'function') {
hls.destroy();
}
} catch (err) { } catch (err) {
console.warn('[TReactPlayer] cleanup warning:', err); console.warn('[TReactPlayer] cleanup warning:', err);
} }

View File

@@ -103,6 +103,18 @@ export const playReducer = (state = initialState, action) => {
subTitleBlobs: {}, 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: { case types.UPDATE_VIDEO_PLAY_STATE: {
const newState = { const newState = {
...state.videoPlayState, ...state.videoPlayState,

View File

@@ -34,6 +34,7 @@ import * as PanelActions from '../../actions/panelActions';
import { updatePanel } from '../../actions/panelActions'; import { updatePanel } from '../../actions/panelActions';
import { import {
CLEAR_PLAYER_INFO, CLEAR_PLAYER_INFO,
clearSubtitleBlob,
getChatLog, getChatLog,
getSubTitle, getSubTitle,
startVideoPlayer, startVideoPlayer,
@@ -261,6 +262,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
const liveShowInfos = USE_SELECTOR('liveShowInfos', (state) => state.main.liveShowInfos); const liveShowInfos = USE_SELECTOR('liveShowInfos', (state) => state.main.liveShowInfos);
const vodSubtitleData = USE_SELECTOR('vodSubtitleData', (state) => state.play.subTitleBlobs); const vodSubtitleData = USE_SELECTOR('vodSubtitleData', (state) => state.play.subTitleBlobs);
const previousSubtitleBlobRef = useRef(null); const previousSubtitleBlobRef = useRef(null);
const previousSubtitleUrlRef = useRef(null);
const broadcast = USE_SELECTOR('broadcast', (state) => state.common.broadcast); const broadcast = USE_SELECTOR('broadcast', (state) => state.common.broadcast);
const videoPlayState = USE_SELECTOR('videoPlayState', (state) => state.play.videoPlayState); const videoPlayState = USE_SELECTOR('videoPlayState', (state) => state.play.videoPlayState);
@@ -1493,6 +1495,13 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
} }
}, [panelInfo?.shptmBanrTpNm, playListInfo]); }, [panelInfo?.shptmBanrTpNm, playListInfo]);
// 컴포넌트 언마운트 시 Job 정리
useEffect(() => {
return () => {
initialFocusTimeoutJob.current?.stop?.();
};
}, []);
// live subtitle Luna API // live subtitle Luna API
useEffect(() => { useEffect(() => {
if (currentSubtitleBlob) { if (currentSubtitleBlob) {
@@ -1953,7 +1962,23 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
if (currentSubtitleUrl) { if (currentSubtitleUrl) {
dispatch(getSubTitle({ showSubtitleUrl: 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(() => { useEffect(() => {
setVideoLoaded(false); setVideoLoaded(false);