[251125] memory monitoring
This commit is contained in:
@@ -66,10 +66,12 @@ import TReactPlayer from './TReactPlayer';
|
||||
import Video from './Video';
|
||||
import css from './VideoPlayer.module.less';
|
||||
import { updateVideoPlayState } from '../../actions/playActions';
|
||||
import createMemoryMonitor from '../../utils/memoryMonitor';
|
||||
|
||||
// 디버그 헬퍼 설정
|
||||
const DEBUG_MODE = false;
|
||||
const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE);
|
||||
const memoryMonitor = createMemoryMonitor();
|
||||
|
||||
const isEnter = is('enter');
|
||||
const isLeft = is('left');
|
||||
@@ -826,6 +828,7 @@ const VideoPlayerBase = class extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
memoryMonitor.logMemory('[VideoPlayer] componentDidMount');
|
||||
on('mousemove', this.activityDetected);
|
||||
if (platform.touch) {
|
||||
on('touchmove', this.activityDetected);
|
||||
@@ -1021,6 +1024,7 @@ const VideoPlayerBase = class extends React.Component {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
memoryMonitor.logMemory('[VideoPlayer] componentWillUnmount - start cleanup', { src: this.props?.src });
|
||||
// console.log('[VideoPlayer] componentWillUnmount - start cleanup', { src: this.props?.src });
|
||||
off('mousemove', this.activityDetected);
|
||||
if (platform.touch) {
|
||||
@@ -1128,6 +1132,12 @@ const VideoPlayerBase = class extends React.Component {
|
||||
}
|
||||
// 레퍼런스도 해제해 GC 대상이 되도록 함
|
||||
this.video = null;
|
||||
// 메모리 모니터링 인터벌 정리
|
||||
if (this.memoryMonitoringInterval) {
|
||||
clearInterval(this.memoryMonitoringInterval);
|
||||
this.memoryMonitoringInterval = null;
|
||||
}
|
||||
memoryMonitor.logMemory('[VideoPlayer] componentWillUnmount - cleanup done');
|
||||
// console.log('[VideoPlayer] componentWillUnmount - cleanup done', { src: this.props?.src });
|
||||
if (this.floatingLayerController) {
|
||||
this.floatingLayerController.unregister();
|
||||
@@ -1564,6 +1574,14 @@ const VideoPlayerBase = class extends React.Component {
|
||||
handleEvent = (ev) => {
|
||||
const el = this.video;
|
||||
|
||||
// 재생 종료 또는 오류 시 메모리 모니터링 타이머 정리
|
||||
if (ev.type === 'ended' || ev.type === 'error') {
|
||||
if (this.memoryMonitoringInterval) {
|
||||
clearInterval(this.memoryMonitoringInterval);
|
||||
this.memoryMonitoringInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
const updatedState = {
|
||||
// Standard media properties
|
||||
currentTime: 0,
|
||||
@@ -1798,6 +1816,11 @@ const VideoPlayerBase = class extends React.Component {
|
||||
* @public
|
||||
*/
|
||||
play = () => {
|
||||
console.log('[TEST] play() method called');
|
||||
memoryMonitor.logMemory('[VideoPlayer] play() called', {
|
||||
currentTime: this.state.currentTime,
|
||||
duration: this.state.duration,
|
||||
});
|
||||
dlog('🟢 [PlayerPanel][VideoPlayer] play() called', {
|
||||
currentTime: this.state.currentTime,
|
||||
duration: this.state.duration,
|
||||
@@ -1819,6 +1842,21 @@ const VideoPlayerBase = class extends React.Component {
|
||||
this.send('play');
|
||||
this.announce($L('Play'));
|
||||
this.startDelayedMiniFeedbackHide(5000);
|
||||
// 재생 시작 시 정기적 메모리 모니터링 시작
|
||||
if (!this.memoryMonitoringInterval) {
|
||||
this.memoryMonitoringInterval = setInterval(() => {
|
||||
try {
|
||||
const mediaState = this.getMediaState();
|
||||
memoryMonitor.logMemory('[VideoPlayer] Playing', {
|
||||
currentTime: (mediaState?.currentTime ?? 0).toFixed(2),
|
||||
duration: (mediaState?.duration ?? 0).toFixed(2),
|
||||
buffered: (this.state?.proportionLoaded ?? 0).toFixed(2),
|
||||
});
|
||||
} catch (err) {
|
||||
// 타이머 실행 중 오류 발생 시 무시
|
||||
}
|
||||
}, 30000); // 30초마다 메모리 확인
|
||||
}
|
||||
|
||||
// Redux 상태 업데이트 - 재생 상태로 변경
|
||||
if (this.props.dispatch) {
|
||||
@@ -1842,6 +1880,10 @@ const VideoPlayerBase = class extends React.Component {
|
||||
* @public
|
||||
*/
|
||||
pause = () => {
|
||||
memoryMonitor.logMemory('[VideoPlayer] pause() called', {
|
||||
currentTime: this.state.currentTime.toFixed(2),
|
||||
duration: this.state.duration.toFixed(2),
|
||||
});
|
||||
dlog('🔴 [VideoPlayer] pause() called', {
|
||||
currentTime: this.state.currentTime,
|
||||
duration: this.state.duration,
|
||||
@@ -1863,6 +1905,11 @@ const VideoPlayerBase = class extends React.Component {
|
||||
this.send('pause');
|
||||
this.announce($L('Pause'));
|
||||
this.stopDelayedMiniFeedbackHide();
|
||||
// 재생 일시정지 시 정기적 메모리 모니터링 중지
|
||||
if (this.memoryMonitoringInterval) {
|
||||
clearInterval(this.memoryMonitoringInterval);
|
||||
this.memoryMonitoringInterval = null;
|
||||
}
|
||||
|
||||
// Redux 상태 업데이트 - 일시정지 상태로 변경
|
||||
if (this.props.dispatch) {
|
||||
|
||||
191
com.twin.app.shoptime/src/utils/memoryMonitor.js
Normal file
191
com.twin.app.shoptime/src/utils/memoryMonitor.js
Normal file
@@ -0,0 +1,191 @@
|
||||
/**
|
||||
* 메모리 모니터링 유틸리티
|
||||
* [Memory] 태그를 붙인 로그로 메모리 사용량을 추적합니다
|
||||
*/
|
||||
|
||||
let memoryMonitorInstance = null;
|
||||
let initialized = false;
|
||||
|
||||
export const createMemoryMonitor = (enableInitLog = true) => {
|
||||
// 싱글톤 패턴: 이미 생성된 인스턴스가 있으면 재사용
|
||||
if (memoryMonitorInstance) {
|
||||
return memoryMonitorInstance;
|
||||
}
|
||||
|
||||
if (enableInitLog && !initialized) {
|
||||
initialized = true;
|
||||
const timestamp = new Date().toISOString();
|
||||
console.log(`[Memory Monitor Initialized] ${timestamp}`);
|
||||
if (typeof performance !== 'undefined' && performance.memory) {
|
||||
console.log(`[Memory] API Support: YES - performance.memory available`);
|
||||
} else {
|
||||
console.log(`[Memory] API Support: NO - performance.memory NOT available (webOS TV 또는 제한된 브라우저)`);
|
||||
}
|
||||
}
|
||||
const getMemoryInfo = () => {
|
||||
if (typeof performance !== 'undefined' && performance.memory) {
|
||||
return {
|
||||
usedJSHeapSize: (performance.memory.usedJSHeapSize / 1048576).toFixed(2),
|
||||
totalJSHeapSize: (performance.memory.totalJSHeapSize / 1048576).toFixed(2),
|
||||
jsHeapSizeLimit: (performance.memory.jsHeapSizeLimit / 1048576).toFixed(2),
|
||||
};
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const formatMemoryLog = (usedMB, totalMB, limitMB) => {
|
||||
const percentage = ((usedMB / limitMB) * 100).toFixed(1);
|
||||
return `[Memory] Used: ${usedMB}MB / Total: ${totalMB}MB / Limit: ${limitMB}MB (${percentage}%)`;
|
||||
};
|
||||
|
||||
return {
|
||||
/**
|
||||
* 현재 메모리 상태를 로깅
|
||||
* @param {string} context - 컨텍스트 설명
|
||||
* @param {object} additionalInfo - 추가 정보
|
||||
*/
|
||||
logMemory: (context = '', additionalInfo = {}) => {
|
||||
const mem = getMemoryInfo();
|
||||
if (mem) {
|
||||
const logMsg = formatMemoryLog(mem.usedJSHeapSize, mem.totalJSHeapSize, mem.jsHeapSizeLimit);
|
||||
const info = Object.keys(additionalInfo).length > 0 ? JSON.stringify(additionalInfo) : '';
|
||||
console.log(`${logMsg} | ${context} ${info}`);
|
||||
} else {
|
||||
const timestamp = new Date().toISOString();
|
||||
console.log(`[Memory] [${timestamp}] ${context} - Browser does not support performance.memory API (추가정보: ${JSON.stringify(additionalInfo)})`);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 메모리 사용량 변화를 추적
|
||||
* @param {string} context - 컨텍스트 설명
|
||||
* @param {number} previousMB - 이전 메모리 사용량 (MB)
|
||||
* @returns {number} 현재 메모리 사용량 (MB)
|
||||
*/
|
||||
trackMemoryDelta: (context = '', previousMB = 0) => {
|
||||
const mem = getMemoryInfo();
|
||||
if (mem) {
|
||||
const currentMB = parseFloat(mem.usedJSHeapSize);
|
||||
const delta = (currentMB - previousMB).toFixed(2);
|
||||
const deltaSign = delta > 0 ? '+' : '';
|
||||
console.log(
|
||||
`[Memory] ${context} | Current: ${currentMB}MB (${deltaSign}${delta}MB) | Total: ${mem.totalJSHeapSize}MB / Limit: ${mem.jsHeapSizeLimit}MB`
|
||||
);
|
||||
return currentMB;
|
||||
}
|
||||
return previousMB;
|
||||
},
|
||||
|
||||
/**
|
||||
* 정기적으로 메모리를 모니터링
|
||||
* @param {number} intervalMs - 모니터링 간격 (기본값: 10000ms)
|
||||
* @param {string} label - 모니터링 라벨
|
||||
* @returns {function} cleanup 함수
|
||||
*/
|
||||
startPeriodicMonitoring: (intervalMs = 10000, label = 'Periodic') => {
|
||||
let lastMemory = 0;
|
||||
const mem = getMemoryInfo();
|
||||
if (mem) lastMemory = parseFloat(mem.usedJSHeapSize);
|
||||
|
||||
const intervalId = setInterval(() => {
|
||||
lastMemory = this.trackMemoryDelta(`${label}:`, lastMemory);
|
||||
}, intervalMs);
|
||||
|
||||
return () => clearInterval(intervalId);
|
||||
},
|
||||
|
||||
/**
|
||||
* 버퍼 관련 메모리 정보 로깅
|
||||
* @param {string} context - 컨텍스트
|
||||
* @param {object} bufferInfo - 버퍼 정보 { bufferedSegments, totalDuration, etc }
|
||||
*/
|
||||
logBufferMemory: (context = '', bufferInfo = {}) => {
|
||||
const mem = getMemoryInfo();
|
||||
if (mem) {
|
||||
const logMsg = formatMemoryLog(mem.usedJSHeapSize, mem.totalJSHeapSize, mem.jsHeapSizeLimit);
|
||||
const bufferStr = JSON.stringify(bufferInfo);
|
||||
console.log(`${logMsg} | Buffer: ${context} | Info: ${bufferStr}`);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* HLS 상태에 따른 메모리 로깅
|
||||
* @param {string} context - 컨텍스트
|
||||
* @param {object} hlsState - HLS 상태 정보
|
||||
*/
|
||||
logHlsMemory: (context = '', hlsState = {}) => {
|
||||
const mem = getMemoryInfo();
|
||||
if (mem) {
|
||||
const logMsg = formatMemoryLog(mem.usedJSHeapSize, mem.totalJSHeapSize, mem.jsHeapSizeLimit);
|
||||
const hlsStr = JSON.stringify(hlsState);
|
||||
console.log(`${logMsg} | HLS: ${context} | State: ${hlsStr}`);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 메모리 정보만 반환 (로깅 없음)
|
||||
* @returns {object} 메모리 정보 객체
|
||||
*/
|
||||
getMemory: () => getMemoryInfo(),
|
||||
};
|
||||
|
||||
// 싱글톤 인스턴스 저장
|
||||
memoryMonitorInstance = {
|
||||
logMemory: (context = '', additionalInfo = {}) => {
|
||||
const mem = getMemoryInfo();
|
||||
if (mem) {
|
||||
const logMsg = formatMemoryLog(mem.usedJSHeapSize, mem.totalJSHeapSize, mem.jsHeapSizeLimit);
|
||||
const info = Object.keys(additionalInfo).length > 0 ? JSON.stringify(additionalInfo) : '';
|
||||
console.log(`${logMsg} | ${context} ${info}`);
|
||||
} else {
|
||||
const timestamp = new Date().toISOString();
|
||||
console.log(`[Memory] [${timestamp}] ${context} - Browser does not support performance.memory API (추가정보: ${JSON.stringify(additionalInfo)})`);
|
||||
}
|
||||
},
|
||||
trackMemoryDelta: (context = '', previousMB = 0) => {
|
||||
const mem = getMemoryInfo();
|
||||
if (mem) {
|
||||
const currentMB = parseFloat(mem.usedJSHeapSize);
|
||||
const delta = (currentMB - previousMB).toFixed(2);
|
||||
const deltaSign = delta > 0 ? '+' : '';
|
||||
console.log(
|
||||
`[Memory] ${context} | Current: ${currentMB}MB (${deltaSign}${delta}MB) | Total: ${mem.totalJSHeapSize}MB / Limit: ${mem.jsHeapSizeLimit}MB`
|
||||
);
|
||||
return currentMB;
|
||||
}
|
||||
return previousMB;
|
||||
},
|
||||
startPeriodicMonitoring: (intervalMs = 30000, label = 'Periodic') => {
|
||||
let lastMemory = 0;
|
||||
const mem = getMemoryInfo();
|
||||
if (mem) lastMemory = parseFloat(mem.usedJSHeapSize);
|
||||
|
||||
const intervalId = setInterval(() => {
|
||||
this.trackMemoryDelta(`${label}:`, lastMemory);
|
||||
}, intervalMs);
|
||||
|
||||
return () => clearInterval(intervalId);
|
||||
},
|
||||
logBufferMemory: (context = '', bufferInfo = {}) => {
|
||||
const mem = getMemoryInfo();
|
||||
if (mem) {
|
||||
const logMsg = formatMemoryLog(mem.usedJSHeapSize, mem.totalJSHeapSize, mem.jsHeapSizeLimit);
|
||||
const bufferStr = JSON.stringify(bufferInfo);
|
||||
console.log(`${logMsg} | Buffer: ${context} | Info: ${bufferStr}`);
|
||||
}
|
||||
},
|
||||
logHlsMemory: (context = '', hlsState = {}) => {
|
||||
const mem = getMemoryInfo();
|
||||
if (mem) {
|
||||
const logMsg = formatMemoryLog(mem.usedJSHeapSize, mem.totalJSHeapSize, mem.jsHeapSizeLimit);
|
||||
const hlsStr = JSON.stringify(hlsState);
|
||||
console.log(`${logMsg} | HLS: ${context} | State: ${hlsStr}`);
|
||||
}
|
||||
},
|
||||
getMemory: () => getMemoryInfo(),
|
||||
};
|
||||
|
||||
return memoryMonitorInstance;
|
||||
};
|
||||
|
||||
export default createMemoryMonitor;
|
||||
@@ -62,6 +62,7 @@ import css from './PlayerPanel.module.less';
|
||||
import PlayerTabButton from './PlayerTabContents/TabButton/PlayerTabButton';
|
||||
import TabContainer from './PlayerTabContents/TabContainer';
|
||||
import TabContainerV2 from './PlayerTabContents/v2/TabContainer.v2';
|
||||
import createMemoryMonitor from '../../utils/memoryMonitor';
|
||||
// import LiveShowContainer from './PlayerTabContents/v2/LiveShowContainer';
|
||||
// import ShopNowContainer from './PlayerTabContents/v2/ShopNowContainer';
|
||||
// import ShopNowButton from './PlayerTabContents/v2/ShopNowButton';
|
||||
@@ -179,6 +180,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
const focusReturnRef = useRef(null);
|
||||
const modalPrevRef = useRef(panelInfo?.modal);
|
||||
const prevIsTopPanelDetailFromPlayerRef = useRef(false);
|
||||
const memoryMonitor = useRef(null);
|
||||
const [playListInfo, setPlayListInfo] = USE_STATE('playListInfo', '');
|
||||
const [shopNowInfo, setShopNowInfo] = USE_STATE('shopNowInfo');
|
||||
const [backupInitialIndex, setBackupInitialIndex] = USE_STATE('backupInitialIndex', 0);
|
||||
@@ -370,6 +372,14 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
panelInfoRef.current = panelInfo;
|
||||
}, [panelInfo]);
|
||||
|
||||
// memoryMonitor 초기화 (마운트 시 한 번만)
|
||||
useEffect(() => {
|
||||
if (!memoryMonitor.current) {
|
||||
memoryMonitor.current = createMemoryMonitor(false); // 초기 로그 비활성화
|
||||
console.log('[PlayerPanel] Memory monitor initialized');
|
||||
}
|
||||
}, []);
|
||||
|
||||
// PlayerPanel.jsx의 라인 313-327 useEffect 수정 - detailPanelClosed flag 감지 추가
|
||||
useEffect(() => {
|
||||
dlog('[PlayerPanel] 🔍 isOnTop useEffect 호출:', {
|
||||
@@ -1733,6 +1743,24 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
setCurrentTime(videoPlayer.current?.getMediaState()?.currentTime);
|
||||
break;
|
||||
}
|
||||
case 'onBuffer': {
|
||||
// 버퍼링 시작 시 메모리 상태 로깅
|
||||
memoryMonitor.current.logMemory('[Video Buffer Start]', {
|
||||
currentTime: videoPlayer.current?.getMediaState()?.currentTime?.toFixed(2),
|
||||
duration: videoPlayer.current?.getMediaState()?.duration?.toFixed(2),
|
||||
proportionLoaded: videoPlayer.current?.getMediaState()?.proportionLoaded?.toFixed(2),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'onBufferEnd': {
|
||||
// 버퍼링 종료 시 메모리 상태 로깅
|
||||
memoryMonitor.current.logMemory('[Video Buffer End]', {
|
||||
currentTime: videoPlayer.current?.getMediaState()?.currentTime?.toFixed(2),
|
||||
duration: videoPlayer.current?.getMediaState()?.duration?.toFixed(2),
|
||||
proportionLoaded: videoPlayer.current?.getMediaState()?.proportionLoaded?.toFixed(2),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'error': {
|
||||
dispatch(
|
||||
sendBroadCast({
|
||||
@@ -1746,6 +1774,23 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
const mediaId = videoPlayer.current?.video?.media?.mediaId;
|
||||
setMediaId(mediaId);
|
||||
setVideoLoaded(true);
|
||||
// HLS 인스턴스 정보 로깅
|
||||
try {
|
||||
const hlsInstance = videoPlayer.current?.video?.getInternalPlayer?.('hls');
|
||||
if (hlsInstance) {
|
||||
memoryMonitor.current.logHlsMemory('[Video Loaded] HLS Instance', {
|
||||
hlsVersion: hlsInstance.version,
|
||||
config: {
|
||||
maxBufferLength: hlsInstance.config?.maxBufferLength,
|
||||
maxMaxBufferLength: hlsInstance.config?.maxMaxBufferLength,
|
||||
backBufferLength: hlsInstance.config?.backBufferLength,
|
||||
maxBufferSize: hlsInstance.config?.maxBufferSize,
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// HLS 정보 수집 실패는 무시
|
||||
}
|
||||
dlog(
|
||||
'[PlayerPanel] 🎬 Video Loaded - shptmBanrTpNm:',
|
||||
panelInfoRef.current?.shptmBanrTpNm
|
||||
@@ -1998,6 +2043,17 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
setVideoLoaded(false);
|
||||
}, [currentPlayingUrl]);
|
||||
|
||||
// 메모리 모니터링: 비디오 URL 변경 시
|
||||
useEffect(() => {
|
||||
if (currentPlayingUrl) {
|
||||
memoryMonitor.current.logMemory(`[Video Change] New URL loaded`, {
|
||||
url: currentPlayingUrl.substring(0, 50),
|
||||
isHLS: currentPlayingUrl.includes('.m3u8'),
|
||||
isDASH: currentPlayingUrl.includes('.mpd'),
|
||||
});
|
||||
}
|
||||
}, [currentPlayingUrl]);
|
||||
|
||||
// 비디오가 새로 선택될 때 타이머 초기화
|
||||
useEffect(() => {
|
||||
if (currentPlayingUrl) {
|
||||
|
||||
Reference in New Issue
Block a user