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

🕐 커밋 시간: 2025. 11. 24. 17:35:58

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

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/components/VideoPlayer/TReactPlayer.jsx
  ~ com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.js
  ~ 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:35:58 +09:00
parent 97ac10c675
commit 372334fdfc
4 changed files with 84 additions and 8 deletions

View File

@@ -159,11 +159,19 @@ export default function TReactPlayer({
useEffect(() => { useEffect(() => {
return () => { return () => {
const videoNode = playerRef.current?.getInternalPlayer(); const videoNode = playerRef.current?.getInternalPlayer();
if (videoNode && videoNode.pause) { if (videoNode) {
console.log('[VIDEO-DEBUG] 🧹 비디오 정리 (URL 변경 또는 언마운트)'); try {
videoNode.pause(); // VideoPlayer.js에서 이미 처리하므로 최소한 정리만
videoNode.src = ''; if (videoNode.pause) {
videoNode.srcObject = null; videoNode.pause();
}
// 중복 정리 방지를 위해 조건 더 엄격하게
if (typeof videoNode.stopVideo === 'function' && videoNode.stopVideo !== videoNode.pause) {
videoNode.stopVideo();
}
} catch (err) {
console.warn('[TReactPlayer] cleanup warning:', err);
}
} }
}; };
}, [url]); // ✅ URL 변경 시에도 정리 로직 실행 }, [url]); // ✅ URL 변경 시에도 정리 로직 실행

View File

@@ -1040,6 +1040,54 @@ const VideoPlayerBase = class extends React.Component {
this.announceJob.stop(); this.announceJob.stop();
this.renderBottomControl.stop(); this.renderBottomControl.stop();
this.slider5WayPressJob.stop(); this.slider5WayPressJob.stop();
// 플레이어 자원 정리: 언마운트 시 디코더/버퍼 해제
try {
// 1. 먼저 중지 (가장 중요)
if (this.video?.stopVideo) {
this.video.stopVideo();
} else if (this.video?.pause) {
this.video.pause();
}
// 2. 시간 초기화
if (this.video?.seekTo) {
this.video.seekTo(0);
} else if (this.video) {
this.video.currentTime = 0;
}
// 3. 리소스 해제
if (this.video) {
this.video.src = '';
if ('srcObject' in this.video) {
this.video.srcObject = null;
}
}
// 4. 내부 인스턴스 정리 (마지막)
if (typeof this.video?.getInternalPlayer === 'function') {
try {
const hls = this.video.getInternalPlayer('hls');
if (hls && typeof hls.destroy === 'function') {
hls.destroy();
}
} catch (hlsErr) {
// HLS 정리 실패는 무시
}
try {
const yt = this.video.getInternalPlayer();
if (yt?.stopVideo) {
yt.stopVideo();
}
} catch (ytErr) {
// YouTube 정리 실패는 무시
}
}
} catch (err) {
// 정리 중 에러는 무시하고 언마운트 진행
// console.warn('[VideoPlayer] cleanup error', err);
}
if (this.floatingLayerController) { if (this.floatingLayerController) {
this.floatingLayerController.unregister(); this.floatingLayerController.unregister();
} }

View File

@@ -90,6 +90,13 @@ export const playReducer = (state = initialState, action) => {
}; };
} }
case types.CLEAR_PLAYER_INFO: { case types.CLEAR_PLAYER_INFO: {
// 기존 Blob URL들을 모두 해제
Object.values(state.subTitleBlobs).forEach(blobUrl => {
if (blobUrl && blobUrl.startsWith('blob:')) {
URL.revokeObjectURL(blobUrl);
}
});
return { return {
...state, ...state,
chatData: {}, chatData: {},

View File

@@ -1896,9 +1896,17 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
// 자막 Blob URL 수명 관리: 이전 Blob을 해제해 메모리 누수 방지 // 자막 Blob URL 수명 관리: 이전 Blob을 해제해 메모리 누수 방지
useEffect(() => { useEffect(() => {
const prevBlobUrl = previousSubtitleBlobRef.current; const prevBlobUrl = previousSubtitleBlobRef.current;
if (prevBlobUrl && prevBlobUrl !== currentSubtitleBlob && typeof prevBlobUrl === 'string') {
if (prevBlobUrl.startsWith('blob:')) { // 더 엄격한 타입과 형식 체크
if (prevBlobUrl &&
typeof prevBlobUrl === 'string' &&
prevBlobUrl.startsWith('blob:') &&
prevBlobUrl !== currentSubtitleBlob) {
try {
URL.revokeObjectURL(prevBlobUrl); URL.revokeObjectURL(prevBlobUrl);
dlog('[PlayerPanel] Previous subtitle Blob URL revoked:', prevBlobUrl);
} catch (error) {
derror('[PlayerPanel] Failed to revoke Blob URL:', error);
} }
} }
@@ -1907,7 +1915,12 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
return () => { return () => {
const lastBlobUrl = previousSubtitleBlobRef.current; const lastBlobUrl = previousSubtitleBlobRef.current;
if (lastBlobUrl && typeof lastBlobUrl === 'string' && lastBlobUrl.startsWith('blob:')) { if (lastBlobUrl && typeof lastBlobUrl === 'string' && lastBlobUrl.startsWith('blob:')) {
URL.revokeObjectURL(lastBlobUrl); try {
URL.revokeObjectURL(lastBlobUrl);
dlog('[PlayerPanel] Final subtitle Blob URL revoked:', lastBlobUrl);
} catch (error) {
derror('[PlayerPanel] Failed to revoke final Blob URL:', error);
}
} }
previousSubtitleBlobRef.current = null; previousSubtitleBlobRef.current = null;
}; };