Compare commits
6 Commits
backup-202
...
develop_si
| Author | SHA1 | Date | |
|---|---|---|---|
| a539e2617e | |||
| a9fd3981c8 | |||
| 6d345ddddc | |||
| 943be925a8 | |||
| c88c0cebc8 | |||
| 7baeca9432 |
@@ -66,10 +66,12 @@ import TReactPlayer from './TReactPlayer';
|
|||||||
import Video from './Video';
|
import Video from './Video';
|
||||||
import css from './VideoPlayer.module.less';
|
import css from './VideoPlayer.module.less';
|
||||||
import { updateVideoPlayState } from '../../actions/playActions';
|
import { updateVideoPlayState } from '../../actions/playActions';
|
||||||
|
import createMemoryMonitor from '../../utils/memoryMonitor';
|
||||||
|
|
||||||
// 디버그 헬퍼 설정
|
// 디버그 헬퍼 설정
|
||||||
const DEBUG_MODE = false;
|
const DEBUG_MODE = false;
|
||||||
const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE);
|
const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE);
|
||||||
|
const memoryMonitor = createMemoryMonitor();
|
||||||
|
|
||||||
const isEnter = is('enter');
|
const isEnter = is('enter');
|
||||||
const isLeft = is('left');
|
const isLeft = is('left');
|
||||||
@@ -776,6 +778,7 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
this.sliderKnobProportion = 0;
|
this.sliderKnobProportion = 0;
|
||||||
this.mediaControlsSpotlightId = props.spotlightId + '_mediaControls';
|
this.mediaControlsSpotlightId = props.spotlightId + '_mediaControls';
|
||||||
this.jumpButtonPressed = null;
|
this.jumpButtonPressed = null;
|
||||||
|
this.focusTimer = null;
|
||||||
|
|
||||||
// Re-render-necessary State
|
// Re-render-necessary State
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -825,6 +828,7 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
memoryMonitor.logMemory('[VideoPlayer] componentDidMount');
|
||||||
on('mousemove', this.activityDetected);
|
on('mousemove', this.activityDetected);
|
||||||
if (platform.touch) {
|
if (platform.touch) {
|
||||||
on('touchmove', this.activityDetected);
|
on('touchmove', this.activityDetected);
|
||||||
@@ -1020,6 +1024,7 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
memoryMonitor.logMemory('[VideoPlayer] componentWillUnmount - start cleanup', { src: this.props?.src });
|
||||||
// console.log('[VideoPlayer] componentWillUnmount - start cleanup', { src: this.props?.src });
|
// console.log('[VideoPlayer] componentWillUnmount - start cleanup', { src: this.props?.src });
|
||||||
off('mousemove', this.activityDetected);
|
off('mousemove', this.activityDetected);
|
||||||
if (platform.touch) {
|
if (platform.touch) {
|
||||||
@@ -1038,6 +1043,7 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
this.stopDelayedTitleHide();
|
this.stopDelayedTitleHide();
|
||||||
this.stopDelayedFeedbackHide();
|
this.stopDelayedFeedbackHide();
|
||||||
this.stopDelayedMiniFeedbackHide();
|
this.stopDelayedMiniFeedbackHide();
|
||||||
|
if (this.focusTimer) clearTimeout(this.focusTimer);
|
||||||
this.announceJob.stop();
|
this.announceJob.stop();
|
||||||
this.renderBottomControl.stop();
|
this.renderBottomControl.stop();
|
||||||
this.slider5WayPressJob.stop();
|
this.slider5WayPressJob.stop();
|
||||||
@@ -1126,6 +1132,7 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
}
|
}
|
||||||
// 레퍼런스도 해제해 GC 대상이 되도록 함
|
// 레퍼런스도 해제해 GC 대상이 되도록 함
|
||||||
this.video = null;
|
this.video = null;
|
||||||
|
memoryMonitor.logMemory('[VideoPlayer] componentWillUnmount - cleanup done');
|
||||||
// console.log('[VideoPlayer] componentWillUnmount - cleanup done', { src: this.props?.src });
|
// console.log('[VideoPlayer] componentWillUnmount - cleanup done', { src: this.props?.src });
|
||||||
if (this.floatingLayerController) {
|
if (this.floatingLayerController) {
|
||||||
this.floatingLayerController.unregister();
|
this.floatingLayerController.unregister();
|
||||||
@@ -1562,6 +1569,14 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
handleEvent = (ev) => {
|
handleEvent = (ev) => {
|
||||||
const el = this.video;
|
const el = this.video;
|
||||||
|
|
||||||
|
// 재생 종료 또는 오류 시 메모리 모니터링 타이머 정리
|
||||||
|
if (ev.type === 'ended' || ev.type === 'error') {
|
||||||
|
if (this.memoryMonitoringInterval) {
|
||||||
|
clearInterval(this.memoryMonitoringInterval);
|
||||||
|
this.memoryMonitoringInterval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const updatedState = {
|
const updatedState = {
|
||||||
// Standard media properties
|
// Standard media properties
|
||||||
currentTime: 0,
|
currentTime: 0,
|
||||||
@@ -1796,6 +1811,10 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
play = () => {
|
play = () => {
|
||||||
|
memoryMonitor.logMemory('[VideoPlayer] play() called', {
|
||||||
|
currentTime: this.state.currentTime,
|
||||||
|
duration: this.state.duration,
|
||||||
|
});
|
||||||
dlog('🟢 [PlayerPanel][VideoPlayer] play() called', {
|
dlog('🟢 [PlayerPanel][VideoPlayer] play() called', {
|
||||||
currentTime: this.state.currentTime,
|
currentTime: this.state.currentTime,
|
||||||
duration: this.state.duration,
|
duration: this.state.duration,
|
||||||
@@ -1840,6 +1859,10 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
pause = () => {
|
pause = () => {
|
||||||
|
memoryMonitor.logMemory('[VideoPlayer] pause() called', {
|
||||||
|
currentTime: this.state.currentTime.toFixed(2),
|
||||||
|
duration: this.state.duration.toFixed(2),
|
||||||
|
});
|
||||||
dlog('🔴 [VideoPlayer] pause() called', {
|
dlog('🔴 [VideoPlayer] pause() called', {
|
||||||
currentTime: this.state.currentTime,
|
currentTime: this.state.currentTime,
|
||||||
duration: this.state.duration,
|
duration: this.state.duration,
|
||||||
@@ -2603,11 +2626,11 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
this.showControls();
|
this.showControls();
|
||||||
|
|
||||||
if (this.state.lastFocusedTarget) {
|
if (this.state.lastFocusedTarget) {
|
||||||
setTimeout(() => {
|
this.focusTimer = setTimeout(() => {
|
||||||
Spotlight.focus(this.state.lastFocusedTarget);
|
Spotlight.focus(this.state.lastFocusedTarget);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setTimeout(() => {
|
this.focusTimer = setTimeout(() => {
|
||||||
Spotlight.focus(SpotlightIds.PLAYER_TAB_BUTTON);
|
Spotlight.focus(SpotlightIds.PLAYER_TAB_BUTTON);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
705
com.twin.app.shoptime/src/utils/memoryMonitor.js
Normal file
705
com.twin.app.shoptime/src/utils/memoryMonitor.js
Normal file
@@ -0,0 +1,705 @@
|
|||||||
|
/**
|
||||||
|
* 메모리 모니터링 유틸리티
|
||||||
|
* [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 getMediaMemoryInfo = () => {
|
||||||
|
try {
|
||||||
|
const mediaElements = document.querySelectorAll('video, audio');
|
||||||
|
let totalVideoBuffer = 0;
|
||||||
|
let totalAudioBuffer = 0;
|
||||||
|
let videoCount = 0;
|
||||||
|
let audioCount = 0;
|
||||||
|
const mediaInfo = [];
|
||||||
|
|
||||||
|
// NodeList를 배열로 변환하여 forEach 사용
|
||||||
|
Array.from(mediaElements).forEach((media, index) => {
|
||||||
|
try {
|
||||||
|
const buffered = media.buffered;
|
||||||
|
let totalDuration = 0;
|
||||||
|
|
||||||
|
if (buffered && buffered.length) {
|
||||||
|
for (let i = 0; i < buffered.length; i++) {
|
||||||
|
try {
|
||||||
|
totalDuration += buffered.end(i) - buffered.start(i);
|
||||||
|
} catch (e) {
|
||||||
|
// buffered 접근 중 오류 발생 시 무시
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (media.tagName === 'VIDEO') {
|
||||||
|
videoCount++;
|
||||||
|
} else if (media.tagName === 'AUDIO') {
|
||||||
|
audioCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 비디오 메타데이터 수집
|
||||||
|
let videoBitrate = 0;
|
||||||
|
let codecInfo = 'unknown';
|
||||||
|
|
||||||
|
if (media.tagName === 'VIDEO' && media.videoWidth && media.videoHeight) {
|
||||||
|
// 해상도 기반 비트레이트 추정 (HLS 스트리밍 기준)
|
||||||
|
const resolution = media.videoWidth * media.videoHeight;
|
||||||
|
if (resolution >= 3840 * 2160) { // 4K
|
||||||
|
videoBitrate = 15000000; // 15Mbps
|
||||||
|
codecInfo = '4K/HLS';
|
||||||
|
} else if (resolution >= 1920 * 1080) { // FHD
|
||||||
|
videoBitrate = 8000000; // 8Mbps
|
||||||
|
codecInfo = 'FHD/HLS';
|
||||||
|
} else if (resolution >= 1280 * 720) { // HD
|
||||||
|
videoBitrate = 4000000; // 4Mbps
|
||||||
|
codecInfo = 'HD/HLS';
|
||||||
|
} else { // SD
|
||||||
|
videoBitrate = 2000000; // 2Mbps
|
||||||
|
codecInfo = 'SD/HLS';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HLS 스트리밍 정보 확인
|
||||||
|
let hlsInfo = null;
|
||||||
|
if (media.src && media.src.includes('.m3u8')) {
|
||||||
|
hlsInfo = {
|
||||||
|
isHLS: true,
|
||||||
|
playlistUrl: media.src.substring(0, 100) + '...',
|
||||||
|
estimatedSegments: Math.ceil((media.duration || 0) / 10), // 10초 세그먼트 기준
|
||||||
|
};
|
||||||
|
} else if (media.src) {
|
||||||
|
hlsInfo = {
|
||||||
|
isHLS: false,
|
||||||
|
contentType: 'progressive',
|
||||||
|
format: media.src.includes('.mp4') ? 'MP4' : 'Unknown',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const mediaData = {
|
||||||
|
index,
|
||||||
|
type: media.tagName ? media.tagName.toLowerCase() : 'unknown',
|
||||||
|
src: media.src ? (media.src.length > 50 ? media.src.substring(0, 50) + '...' : media.src) : 'N/A',
|
||||||
|
duration: media.duration || 0,
|
||||||
|
bufferedDuration: totalDuration,
|
||||||
|
currentTime: media.currentTime || 0,
|
||||||
|
readyState: media.readyState || 0,
|
||||||
|
networkState: media.networkState || 0,
|
||||||
|
videoWidth: media.videoWidth || 0,
|
||||||
|
videoHeight: media.videoHeight || 0,
|
||||||
|
// 비디오 전용 정보
|
||||||
|
bitrate: videoBitrate,
|
||||||
|
codecInfo: codecInfo,
|
||||||
|
// HLS/스트리밍 정보
|
||||||
|
hlsInfo: hlsInfo,
|
||||||
|
// 버퍼 효율성
|
||||||
|
bufferEfficiency: media.duration > 0 ? (totalDuration / media.duration * 100).toFixed(1) + '%' : '0%',
|
||||||
|
// 재생 상태
|
||||||
|
paused: media.paused,
|
||||||
|
ended: media.ended,
|
||||||
|
muted: media.muted,
|
||||||
|
volume: media.volume || 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
mediaInfo.push(mediaData);
|
||||||
|
|
||||||
|
// 실제 버퍼 메모리 계산
|
||||||
|
if (media.tagName === 'VIDEO' && media.videoWidth && media.videoHeight) {
|
||||||
|
// 비디오: 실제 비트레이트 기반 계산
|
||||||
|
totalVideoBuffer += totalDuration * (videoBitrate / 8); // bytes
|
||||||
|
} else if (media.tagName === 'AUDIO') {
|
||||||
|
// 오디오: 고품질 320kbps로 추정
|
||||||
|
totalAudioBuffer += totalDuration * 320000 / 8; // bytes
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// 개별 미디어 요소 처리 중 오류 발생 시 무시
|
||||||
|
console.warn('[Memory Monitor] Error processing media element:', e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
mediaCount: mediaElements.length,
|
||||||
|
videoElements: videoCount,
|
||||||
|
audioElements: audioCount,
|
||||||
|
totalVideoBufferMB: (totalVideoBuffer / 1048576).toFixed(2),
|
||||||
|
totalAudioBufferMB: (totalAudioBuffer / 1048576).toFixed(2),
|
||||||
|
estimatedMediaMemoryMB: ((totalVideoBuffer + totalAudioBuffer) / 1048576).toFixed(2),
|
||||||
|
mediaElements: mediaInfo
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[Memory Monitor] Error getting media memory info:', e);
|
||||||
|
return {
|
||||||
|
mediaCount: 0,
|
||||||
|
videoElements: 0,
|
||||||
|
audioElements: 0,
|
||||||
|
totalVideoBufferMB: '0.00',
|
||||||
|
totalAudioBufferMB: '0.00',
|
||||||
|
estimatedMediaMemoryMB: '0.00',
|
||||||
|
mediaElements: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 이미지 리소스 메모리 정보 수집
|
||||||
|
const getImageMemoryInfo = () => {
|
||||||
|
try {
|
||||||
|
const images = document.querySelectorAll('img');
|
||||||
|
let totalImageMemory = 0;
|
||||||
|
const imageInfo = [];
|
||||||
|
|
||||||
|
// NodeList를 배열로 변환하여 forEach 사용
|
||||||
|
Array.from(images).forEach((img, index) => {
|
||||||
|
try {
|
||||||
|
if (img.naturalWidth && img.naturalHeight) {
|
||||||
|
// 이미지 메모리 크기 추정 (너비 * 높이 * 4바이트 RGBA)
|
||||||
|
const estimatedMemory = img.naturalWidth * img.naturalHeight * 4;
|
||||||
|
totalImageMemory += estimatedMemory;
|
||||||
|
|
||||||
|
imageInfo.push({
|
||||||
|
index,
|
||||||
|
src: img.src ? (img.src.length > 50 ? img.src.substring(0, 50) + '...' : img.src) : 'N/A',
|
||||||
|
naturalWidth: img.naturalWidth,
|
||||||
|
naturalHeight: img.naturalHeight,
|
||||||
|
displayWidth: img.offsetWidth || 0,
|
||||||
|
displayHeight: img.offsetHeight || 0,
|
||||||
|
estimatedMemoryMB: (estimatedMemory / 1048576).toFixed(2),
|
||||||
|
complete: img.complete || false,
|
||||||
|
loading: img.loading || 'auto'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// 개별 이미지 요소 처리 중 오류 발생 시 무시
|
||||||
|
console.warn('[Memory Monitor] Error processing image element:', e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
imageCount: images.length,
|
||||||
|
totalImageMemoryMB: (totalImageMemory / 1048576).toFixed(2),
|
||||||
|
images: imageInfo
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[Memory Monitor] Error getting image memory info:', e);
|
||||||
|
return {
|
||||||
|
imageCount: 0,
|
||||||
|
totalImageMemoryMB: '0.00',
|
||||||
|
images: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Canvas/WebGL 리소스 메모리 정보 수집
|
||||||
|
const getCanvasMemoryInfo = () => {
|
||||||
|
try {
|
||||||
|
const canvases = document.querySelectorAll('canvas');
|
||||||
|
let totalCanvasMemory = 0;
|
||||||
|
const canvasInfo = [];
|
||||||
|
|
||||||
|
// NodeList를 배열로 변환하여 forEach 사용
|
||||||
|
Array.from(canvases).forEach((canvas, index) => {
|
||||||
|
try {
|
||||||
|
const context = canvas.getContext('2d') || canvas.getContext('webgl') || canvas.getContext('webgl2');
|
||||||
|
if (context) {
|
||||||
|
const memory = canvas.width * canvas.height * 4; // 4바이트 per 픽셀
|
||||||
|
totalCanvasMemory += memory;
|
||||||
|
|
||||||
|
canvasInfo.push({
|
||||||
|
index,
|
||||||
|
width: canvas.width || 0,
|
||||||
|
height: canvas.height || 0,
|
||||||
|
contextType: context.constructor.name || 'unknown',
|
||||||
|
estimatedMemoryMB: (memory / 1048576).toFixed(2)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// 개별 캔버스 요소 처리 중 오류 발생 시 무시
|
||||||
|
console.warn('[Memory Monitor] Error processing canvas element:', e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
canvasCount: canvases.length,
|
||||||
|
totalCanvasMemoryMB: (totalCanvasMemory / 1048576).toFixed(2),
|
||||||
|
canvases: canvasInfo
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[Memory Monitor] Error getting canvas memory info:', e);
|
||||||
|
return {
|
||||||
|
canvasCount: 0,
|
||||||
|
totalCanvasMemoryMB: '0.00',
|
||||||
|
canvases: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 통합 미디어 메모리 정보
|
||||||
|
const getCompleteMediaMemoryInfo = () => {
|
||||||
|
const mediaMemory = getMediaMemoryInfo();
|
||||||
|
const imageMemory = getImageMemoryInfo();
|
||||||
|
const canvasMemory = getCanvasMemoryInfo();
|
||||||
|
|
||||||
|
const totalEstimatedMB = (
|
||||||
|
parseFloat(mediaMemory.estimatedMediaMemoryMB) +
|
||||||
|
parseFloat(imageMemory.totalImageMemoryMB) +
|
||||||
|
parseFloat(canvasMemory.totalCanvasMemoryMB)
|
||||||
|
).toFixed(2);
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalEstimatedMediaMemoryMB: totalEstimatedMB,
|
||||||
|
media: mediaMemory,
|
||||||
|
images: imageMemory,
|
||||||
|
canvas: canvasMemory,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDetailedMemoryInfo = () => {
|
||||||
|
const info = getMemoryInfo();
|
||||||
|
if (!info) return null;
|
||||||
|
|
||||||
|
// 추가 메모리 정보
|
||||||
|
const detailed = {
|
||||||
|
...info,
|
||||||
|
// usedJSHeapSize의 percentage (상세)
|
||||||
|
heapUsagePercent: ((parseFloat(info.usedJSHeapSize) / parseFloat(info.jsHeapSizeLimit)) * 100).toFixed(1),
|
||||||
|
// DOM 노드 수
|
||||||
|
domNodeCount: document.querySelectorAll('*').length,
|
||||||
|
// 리스너 수 (대략값)
|
||||||
|
eventListenerEstimate: Object.keys(window).filter(key => key.startsWith('on')).length,
|
||||||
|
// 미디어 리소스 정보 추가
|
||||||
|
mediaMemory: getCompleteMediaMemoryInfo(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return detailed;
|
||||||
|
};
|
||||||
|
|
||||||
|
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}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 상세 메모리 정보 로깅
|
||||||
|
* @param {string} context - 컨텍스트
|
||||||
|
* @param {object} additionalInfo - 추가 정보
|
||||||
|
*/
|
||||||
|
logDetailedMemory: (context = '', additionalInfo = {}) => {
|
||||||
|
const detailed = getDetailedMemoryInfo();
|
||||||
|
if (detailed) {
|
||||||
|
const logMsg = formatMemoryLog(detailed.usedJSHeapSize, detailed.totalJSHeapSize, detailed.jsHeapSizeLimit);
|
||||||
|
const info = Object.keys(additionalInfo).length > 0 ? JSON.stringify(additionalInfo) : '';
|
||||||
|
const detailStr = JSON.stringify({
|
||||||
|
heapUsagePercent: detailed.heapUsagePercent + '%',
|
||||||
|
domNodeCount: detailed.domNodeCount,
|
||||||
|
eventListenerEstimate: detailed.eventListenerEstimate,
|
||||||
|
});
|
||||||
|
const mediaMemory = detailed.mediaMemory;
|
||||||
|
const mediaStr = JSON.stringify({
|
||||||
|
totalMediaMemory: mediaMemory.totalEstimatedMediaMemoryMB + 'MB',
|
||||||
|
videoElements: mediaMemory.media.videoElements,
|
||||||
|
audioElements: mediaMemory.media.audioElements,
|
||||||
|
imageCount: mediaMemory.images.imageCount,
|
||||||
|
imageMemory: mediaMemory.images.totalImageMemoryMB + 'MB',
|
||||||
|
canvasCount: mediaMemory.canvas.canvasCount,
|
||||||
|
canvasMemory: mediaMemory.canvas.totalCanvasMemoryMB + 'MB'
|
||||||
|
});
|
||||||
|
|
||||||
|
const jsTotal = parseFloat(detailed.usedJSHeapSize);
|
||||||
|
const mediaTotal = parseFloat(mediaMemory.totalEstimatedMediaMemoryMB);
|
||||||
|
const estimatedTotal = (jsTotal + mediaTotal).toFixed(2);
|
||||||
|
|
||||||
|
console.log(`${logMsg} | ${context} | Details: ${detailStr} | Media: ${mediaStr} | Est.Total: ${estimatedTotal}MB ${info}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전체 미디어 리소스 메모리 로깅
|
||||||
|
* @param {string} context - 컨텍스트
|
||||||
|
* @param {object} additionalInfo - 추가 정보
|
||||||
|
*/
|
||||||
|
logMediaMemory: (context = '', additionalInfo = {}) => {
|
||||||
|
const jsMem = getMemoryInfo();
|
||||||
|
const mediaMem = getCompleteMediaMemoryInfo();
|
||||||
|
|
||||||
|
if (jsMem && mediaMem) {
|
||||||
|
const jsTotal = parseFloat(jsMem.usedJSHeapSize);
|
||||||
|
const mediaTotal = parseFloat(mediaMem.totalEstimatedMediaMemoryMB);
|
||||||
|
const estimatedTotal = (jsTotal + mediaTotal).toFixed(2);
|
||||||
|
|
||||||
|
const logMsg = formatMemoryLog(jsMem.usedJSHeapSize, jsMem.totalJSHeapSize, jsMem.jsHeapSizeLimit);
|
||||||
|
const info = Object.keys(additionalInfo).length > 0 ? JSON.stringify(additionalInfo) : '';
|
||||||
|
|
||||||
|
console.log(`${logMsg} | Media: ${context}`);
|
||||||
|
console.log(`[Media Breakdown] Images: ${mediaMem.images.totalImageMemoryMB}MB (${mediaMem.images.imageCount}개), Video: ${mediaMem.media.estimatedMediaMemoryMB}MB (${mediaMem.media.mediaCount}개), Canvas: ${mediaMem.canvas.totalCanvasMemoryMB}MB (${mediaMem.canvas.canvasCount}개)`);
|
||||||
|
console.log(`[Total Estimated] JS(${jsTotal}MB) + Media(${mediaTotal}MB) = ${estimatedTotal}MB ${info}`);
|
||||||
|
} else {
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
console.log(`[Media Memory] [${timestamp}] ${context} - Browser does not support performance.memory API (추가정보: ${JSON.stringify(additionalInfo)})`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 비디오 전용 상세 메모리 로깅
|
||||||
|
* @param {string} context - 컨텍스트
|
||||||
|
* @param {object} additionalInfo - 추가 정보
|
||||||
|
*/
|
||||||
|
logVideoMemory: (context = '', additionalInfo = {}) => {
|
||||||
|
const jsMem = getMemoryInfo();
|
||||||
|
const mediaMem = getMediaMemoryInfo();
|
||||||
|
|
||||||
|
if (jsMem && mediaMem) {
|
||||||
|
const logMsg = formatMemoryLog(jsMem.usedJSHeapSize, jsMem.totalJSHeapSize, jsMem.jsHeapSizeLimit);
|
||||||
|
const info = Object.keys(additionalInfo).length > 0 ? JSON.stringify(additionalInfo) : '';
|
||||||
|
|
||||||
|
console.log(`${logMsg} | Video Memory: ${context}`);
|
||||||
|
console.log(`[Video Summary] ${mediaMem.videoElements}개 비디오, ${mediaMem.totalVideoBufferMB}MB 버퍼 메모리 사용`);
|
||||||
|
|
||||||
|
// 개별 비디오 정보 상세 출력
|
||||||
|
mediaMem.mediaElements.forEach((video, idx) => {
|
||||||
|
if (video.type === 'video') {
|
||||||
|
console.log(`[Video ${video.index}] ${video.codecInfo} ${video.videoWidth}x${video.videoHeight} | Buffered: ${video.bufferedDuration.toFixed(1)}s/${video.duration.toFixed(1)}s (${video.bufferEfficiency}) | ${video.hlsInfo?.isHLS ? 'HLS' : 'Progressive'} | ${video.paused ? 'Paused' : 'Playing'} | Src: ${video.src}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`[Video Estimation] JS Heap: ${jsMem.usedJSHeapSize}MB + Video Buffer: ${mediaMem.totalVideoBufferMB}MB = ${(parseFloat(jsMem.usedJSHeapSize) + parseFloat(mediaMem.totalVideoBufferMB)).toFixed(2)}MB ${info}`);
|
||||||
|
} else {
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
console.log(`[Video Memory] [${timestamp}] ${context} - Browser does not support performance.memory API (추가정보: ${JSON.stringify(additionalInfo)})`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HLS 스트리밍 메모리 전용 로깅
|
||||||
|
* @param {string} context - 컨텍스트
|
||||||
|
* @param {object} additionalInfo - 추가 정보
|
||||||
|
*/
|
||||||
|
logHLSMemory: (context = '', additionalInfo = {}) => {
|
||||||
|
const jsMem = getMemoryInfo();
|
||||||
|
const mediaMem = getMediaMemoryInfo();
|
||||||
|
|
||||||
|
if (jsMem && mediaMem) {
|
||||||
|
const hlsVideos = mediaMem.mediaElements.filter(video => video.hlsInfo && video.hlsInfo.isHLS);
|
||||||
|
const progressiveVideos = mediaMem.mediaElements.filter(video => video.hlsInfo && !video.hlsInfo.isHLS);
|
||||||
|
|
||||||
|
const logMsg = formatMemoryLog(jsMem.usedJSHeapSize, jsMem.totalJSHeapSize, jsMem.jsHeapSizeLimit);
|
||||||
|
const info = Object.keys(additionalInfo).length > 0 ? JSON.stringify(additionalInfo) : '';
|
||||||
|
|
||||||
|
console.log(`${logMsg} | HLS Streaming: ${context}`);
|
||||||
|
console.log(`[Streaming Analysis] HLS: ${hlsVideos.length}개, Progressive: ${progressiveVideos.length}개 | Total Video Memory: ${mediaMem.totalVideoBufferMB}MB`);
|
||||||
|
|
||||||
|
// HLS 비디오 상세 정보
|
||||||
|
if (hlsVideos.length > 0) {
|
||||||
|
console.log(`[HLS Videos]`);
|
||||||
|
hlsVideos.forEach(video => {
|
||||||
|
console.log(` ${video.codecInfo} ${video.videoWidth}x${video.videoHeight} | Segments: ~${video.hlsInfo.estimatedSegments}개 | Buffer: ${video.bufferedDuration.toFixed(1)}s | Efficiency: ${video.bufferEfficiency}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Progressive 비디오 상세 정보
|
||||||
|
if (progressiveVideos.length > 0) {
|
||||||
|
console.log(`[Progressive Videos]`);
|
||||||
|
progressiveVideos.forEach(video => {
|
||||||
|
console.log(` ${video.codecInfo} ${video.videoWidth}x${video.videoHeight} | Format: ${video.hlsInfo.format} | Buffer: ${video.bufferedDuration.toFixed(1)}s`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const streamingMemoryMB = hlsVideos.reduce((sum, video) => {
|
||||||
|
return sum + parseFloat(video.bufferedDuration) * (video.bitrate / 8 / 1048576);
|
||||||
|
}, 0).toFixed(2);
|
||||||
|
|
||||||
|
console.log(`[Streaming Memory] HLS Buffer: ${streamingMemoryMB}MB | Progressive Buffer: ${(parseFloat(mediaMem.totalVideoBufferMB) - parseFloat(streamingMemoryMB)).toFixed(2)}MB ${info}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 메모리 정보만 반환 (로깅 없음)
|
||||||
|
* @returns {object} 메모리 정보 객체
|
||||||
|
*/
|
||||||
|
getMemory: () => getMemoryInfo(),
|
||||||
|
getDetailedMemory: () => getDetailedMemoryInfo(),
|
||||||
|
getMediaMemory: () => getCompleteMediaMemoryInfo(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// 싱글톤 인스턴스 저장
|
||||||
|
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}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
logDetailedMemory: (context = '', additionalInfo = {}) => {
|
||||||
|
const detailed = getDetailedMemoryInfo();
|
||||||
|
if (detailed) {
|
||||||
|
const logMsg = formatMemoryLog(detailed.usedJSHeapSize, detailed.totalJSHeapSize, detailed.jsHeapSizeLimit);
|
||||||
|
const info = Object.keys(additionalInfo).length > 0 ? JSON.stringify(additionalInfo) : '';
|
||||||
|
const detailStr = JSON.stringify({
|
||||||
|
heapUsagePercent: detailed.heapUsagePercent + '%',
|
||||||
|
domNodeCount: detailed.domNodeCount,
|
||||||
|
eventListenerEstimate: detailed.eventListenerEstimate,
|
||||||
|
});
|
||||||
|
const mediaMemory = detailed.mediaMemory;
|
||||||
|
const mediaStr = JSON.stringify({
|
||||||
|
totalMediaMemory: mediaMemory.totalEstimatedMediaMemoryMB + 'MB',
|
||||||
|
videoElements: mediaMemory.media.videoElements,
|
||||||
|
audioElements: mediaMemory.media.audioElements,
|
||||||
|
imageCount: mediaMemory.images.imageCount,
|
||||||
|
imageMemory: mediaMemory.images.totalImageMemoryMB + 'MB',
|
||||||
|
canvasCount: mediaMemory.canvas.canvasCount,
|
||||||
|
canvasMemory: mediaMemory.canvas.totalCanvasMemoryMB + 'MB'
|
||||||
|
});
|
||||||
|
|
||||||
|
const jsTotal = parseFloat(detailed.usedJSHeapSize);
|
||||||
|
const mediaTotal = parseFloat(mediaMemory.totalEstimatedMediaMemoryMB);
|
||||||
|
const estimatedTotal = (jsTotal + mediaTotal).toFixed(2);
|
||||||
|
|
||||||
|
console.log(`${logMsg} | ${context} | Details: ${detailStr} | Media: ${mediaStr} | Est.Total: ${estimatedTotal}MB ${info}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
logMediaMemory: (context = '', additionalInfo = {}) => {
|
||||||
|
const jsMem = getMemoryInfo();
|
||||||
|
const mediaMem = getCompleteMediaMemoryInfo();
|
||||||
|
|
||||||
|
if (jsMem && mediaMem) {
|
||||||
|
const jsTotal = parseFloat(jsMem.usedJSHeapSize);
|
||||||
|
const mediaTotal = parseFloat(mediaMem.totalEstimatedMediaMemoryMB);
|
||||||
|
const estimatedTotal = (jsTotal + mediaTotal).toFixed(2);
|
||||||
|
|
||||||
|
const logMsg = formatMemoryLog(jsMem.usedJSHeapSize, jsMem.totalJSHeapSize, jsMem.jsHeapSizeLimit);
|
||||||
|
const info = Object.keys(additionalInfo).length > 0 ? JSON.stringify(additionalInfo) : '';
|
||||||
|
|
||||||
|
console.log(`${logMsg} | Media: ${context}`);
|
||||||
|
console.log(`[Media Breakdown] Images: ${mediaMem.images.totalImageMemoryMB}MB (${mediaMem.images.imageCount}개), Video: ${mediaMem.media.estimatedMediaMemoryMB}MB (${mediaMem.media.mediaCount}개), Canvas: ${mediaMem.canvas.totalCanvasMemoryMB}MB (${mediaMem.canvas.canvasCount}개)`);
|
||||||
|
console.log(`[Total Estimated] JS(${jsTotal}MB) + Media(${mediaTotal}MB) = ${estimatedTotal}MB ${info}`);
|
||||||
|
} else {
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
console.log(`[Media Memory] [${timestamp}] ${context} - Browser does not support performance.memory API (추가정보: ${JSON.stringify(additionalInfo)})`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getMemory: () => getMemoryInfo(),
|
||||||
|
getDetailedMemory: () => getDetailedMemoryInfo(),
|
||||||
|
getMediaMemory: () => getCompleteMediaMemoryInfo(),
|
||||||
|
logVideoMemory: (context = '', additionalInfo = {}) => {
|
||||||
|
const jsMem = getMemoryInfo();
|
||||||
|
const mediaMem = getMediaMemoryInfo();
|
||||||
|
|
||||||
|
if (jsMem && mediaMem) {
|
||||||
|
const logMsg = formatMemoryLog(jsMem.usedJSHeapSize, jsMem.totalJSHeapSize, jsMem.jsHeapSizeLimit);
|
||||||
|
const info = Object.keys(additionalInfo).length > 0 ? JSON.stringify(additionalInfo) : '';
|
||||||
|
|
||||||
|
console.log(`${logMsg} | Video Memory: ${context}`);
|
||||||
|
console.log(`[Video Summary] ${mediaMem.videoElements}개 비디오, ${mediaMem.totalVideoBufferMB}MB 버퍼 메모리 사용`);
|
||||||
|
|
||||||
|
// 개별 비디오 정보 상세 출력
|
||||||
|
mediaMem.mediaElements.forEach((video, idx) => {
|
||||||
|
if (video.type === 'video') {
|
||||||
|
console.log(`[Video ${video.index}] ${video.codecInfo} ${video.videoWidth}x${video.videoHeight} | Buffered: ${video.bufferedDuration.toFixed(1)}s/${video.duration.toFixed(1)}s (${video.bufferEfficiency}) | ${video.hlsInfo?.isHLS ? 'HLS' : 'Progressive'} | ${video.paused ? 'Paused' : 'Playing'} | Src: ${video.src}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`[Video Estimation] JS Heap: ${jsMem.usedJSHeapSize}MB + Video Buffer: ${mediaMem.totalVideoBufferMB}MB = ${(parseFloat(jsMem.usedJSHeapSize) + parseFloat(mediaMem.totalVideoBufferMB)).toFixed(2)}MB ${info}`);
|
||||||
|
} else {
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
console.log(`[Video Memory] [${timestamp}] ${context} - Browser does not support performance.memory API (추가정보: ${JSON.stringify(additionalInfo)})`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
logHLSMemory: (context = '', additionalInfo = {}) => {
|
||||||
|
const jsMem = getMemoryInfo();
|
||||||
|
const mediaMem = getMediaMemoryInfo();
|
||||||
|
|
||||||
|
if (jsMem && mediaMem) {
|
||||||
|
const hlsVideos = mediaMem.mediaElements.filter(video => video.hlsInfo && video.hlsInfo.isHLS);
|
||||||
|
const progressiveVideos = mediaMem.mediaElements.filter(video => video.hlsInfo && !video.hlsInfo.isHLS);
|
||||||
|
|
||||||
|
const logMsg = formatMemoryLog(jsMem.usedJSHeapSize, jsMem.totalJSHeapSize, jsMem.jsHeapSizeLimit);
|
||||||
|
const info = Object.keys(additionalInfo).length > 0 ? JSON.stringify(additionalInfo) : '';
|
||||||
|
|
||||||
|
console.log(`${logMsg} | HLS Streaming: ${context}`);
|
||||||
|
console.log(`[Streaming Analysis] HLS: ${hlsVideos.length}개, Progressive: ${progressiveVideos.length}개 | Total Video Memory: ${mediaMem.totalVideoBufferMB}MB`);
|
||||||
|
|
||||||
|
// HLS 비디오 상세 정보
|
||||||
|
if (hlsVideos.length > 0) {
|
||||||
|
console.log(`[HLS Videos]`);
|
||||||
|
hlsVideos.forEach(video => {
|
||||||
|
console.log(` ${video.codecInfo} ${video.videoWidth}x${video.videoHeight} | Segments: ~${video.hlsInfo.estimatedSegments}개 | Buffer: ${video.bufferedDuration.toFixed(1)}s | Efficiency: ${video.bufferEfficiency}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Progressive 비디오 상세 정보
|
||||||
|
if (progressiveVideos.length > 0) {
|
||||||
|
console.log(`[Progressive Videos]`);
|
||||||
|
progressiveVideos.forEach(video => {
|
||||||
|
console.log(` ${video.codecInfo} ${video.videoWidth}x${video.videoHeight} | Format: ${video.hlsInfo.format} | Buffer: ${video.bufferedDuration.toFixed(1)}s`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const streamingMemoryMB = hlsVideos.reduce((sum, video) => {
|
||||||
|
return sum + parseFloat(video.bufferedDuration) * (video.bitrate / 8 / 1048576);
|
||||||
|
}, 0).toFixed(2);
|
||||||
|
|
||||||
|
console.log(`[Streaming Memory] HLS Buffer: ${streamingMemoryMB}MB | Progressive Buffer: ${(parseFloat(mediaMem.totalVideoBufferMB) - parseFloat(streamingMemoryMB)).toFixed(2)}MB ${info}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return memoryMonitorInstance;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default createMemoryMonitor;
|
||||||
@@ -62,6 +62,7 @@ import css from './PlayerPanel.module.less';
|
|||||||
import PlayerTabButton from './PlayerTabContents/TabButton/PlayerTabButton';
|
import PlayerTabButton from './PlayerTabContents/TabButton/PlayerTabButton';
|
||||||
import TabContainer from './PlayerTabContents/TabContainer';
|
import TabContainer from './PlayerTabContents/TabContainer';
|
||||||
import TabContainerV2 from './PlayerTabContents/v2/TabContainer.v2';
|
import TabContainerV2 from './PlayerTabContents/v2/TabContainer.v2';
|
||||||
|
import createMemoryMonitor from '../../utils/memoryMonitor';
|
||||||
// import LiveShowContainer from './PlayerTabContents/v2/LiveShowContainer';
|
// import LiveShowContainer from './PlayerTabContents/v2/LiveShowContainer';
|
||||||
// import ShopNowContainer from './PlayerTabContents/v2/ShopNowContainer';
|
// import ShopNowContainer from './PlayerTabContents/v2/ShopNowContainer';
|
||||||
// import ShopNowButton from './PlayerTabContents/v2/ShopNowButton';
|
// import ShopNowButton from './PlayerTabContents/v2/ShopNowButton';
|
||||||
@@ -179,6 +180,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
const focusReturnRef = useRef(null);
|
const focusReturnRef = useRef(null);
|
||||||
const modalPrevRef = useRef(panelInfo?.modal);
|
const modalPrevRef = useRef(panelInfo?.modal);
|
||||||
const prevIsTopPanelDetailFromPlayerRef = useRef(false);
|
const prevIsTopPanelDetailFromPlayerRef = useRef(false);
|
||||||
|
const memoryMonitor = useRef(null);
|
||||||
const [playListInfo, setPlayListInfo] = USE_STATE('playListInfo', '');
|
const [playListInfo, setPlayListInfo] = USE_STATE('playListInfo', '');
|
||||||
const [shopNowInfo, setShopNowInfo] = USE_STATE('shopNowInfo');
|
const [shopNowInfo, setShopNowInfo] = USE_STATE('shopNowInfo');
|
||||||
const [backupInitialIndex, setBackupInitialIndex] = USE_STATE('backupInitialIndex', 0);
|
const [backupInitialIndex, setBackupInitialIndex] = USE_STATE('backupInitialIndex', 0);
|
||||||
@@ -282,6 +284,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
const watchIntervalLive = useRef(null);
|
const watchIntervalLive = useRef(null);
|
||||||
const watchIntervalVod = useRef(null);
|
const watchIntervalVod = useRef(null);
|
||||||
const watchIntervalMedia = useRef(null);
|
const watchIntervalMedia = useRef(null);
|
||||||
|
const timeoutRef = useRef(null);
|
||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
// console.log("###videoLoaded", videoLoaded);
|
// console.log("###videoLoaded", videoLoaded);
|
||||||
// if (nowMenu) {
|
// if (nowMenu) {
|
||||||
@@ -369,6 +372,32 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
panelInfoRef.current = panelInfo;
|
panelInfoRef.current = panelInfo;
|
||||||
}, [panelInfo]);
|
}, [panelInfo]);
|
||||||
|
|
||||||
|
// memoryMonitor 초기화 (마운트 시 한 번만)
|
||||||
|
useEffect(() => {
|
||||||
|
if (!memoryMonitor.current) {
|
||||||
|
memoryMonitor.current = createMemoryMonitor(false); // 초기 로그 비활성화
|
||||||
|
console.log('[PlayerPanel] Memory monitor initialized');
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 재생 중 15초마다 메모리 모니터링 (상세 정보 포함)
|
||||||
|
useEffect(() => {
|
||||||
|
const memoryLogInterval = setInterval(() => {
|
||||||
|
const mediaState = videoPlayer.current?.getMediaState();
|
||||||
|
|
||||||
|
// 재생 중인지 확인 (duration > 0이고 paused가 아님)
|
||||||
|
if (mediaState?.duration > 0 && !mediaState?.paused && mediaState?.currentTime > 0) {
|
||||||
|
memoryMonitor.current.logDetailedMemory('[Video Playing]', {
|
||||||
|
currentTime: (mediaState?.currentTime ?? 0).toFixed(2),
|
||||||
|
duration: (mediaState?.duration ?? 0).toFixed(2),
|
||||||
|
buffered: (mediaState?.proportionLoaded ?? 0).toFixed(2),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 15000); // 15초마다 체크
|
||||||
|
|
||||||
|
return () => clearInterval(memoryLogInterval);
|
||||||
|
}, []);
|
||||||
|
|
||||||
// PlayerPanel.jsx의 라인 313-327 useEffect 수정 - detailPanelClosed flag 감지 추가
|
// PlayerPanel.jsx의 라인 313-327 useEffect 수정 - detailPanelClosed flag 감지 추가
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dlog('[PlayerPanel] 🔍 isOnTop useEffect 호출:', {
|
dlog('[PlayerPanel] 🔍 isOnTop useEffect 호출:', {
|
||||||
@@ -619,7 +648,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
|
|
||||||
if (lastFocusedTargetId) {
|
if (lastFocusedTargetId) {
|
||||||
// ShopNowContents가 렌더링될 때까지 대기 후 포커스 복원
|
// ShopNowContents가 렌더링될 때까지 대기 후 포커스 복원
|
||||||
setTimeout(() => {
|
timeoutRef.current = setTimeout(() => {
|
||||||
dlog('[PlayerPanel] 🔍 800ms 후 포커스 복원 시도:', lastFocusedTargetId);
|
dlog('[PlayerPanel] 🔍 800ms 후 포커스 복원 시도:', lastFocusedTargetId);
|
||||||
Spotlight.focus(lastFocusedTargetId);
|
Spotlight.focus(lastFocusedTargetId);
|
||||||
}, 800);
|
}, 800);
|
||||||
@@ -1147,7 +1176,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
|
|
||||||
//딮링크로 플레이어 진입 후 이전버튼 클릭시
|
//딮링크로 플레이어 진입 후 이전버튼 클릭시
|
||||||
if (panels.length === 1) {
|
if (panels.length === 1) {
|
||||||
setTimeout(() => {
|
timeoutRef.current = setTimeout(() => {
|
||||||
Spotlight.focus(SpotlightIds.HOME_TBODY);
|
Spotlight.focus(SpotlightIds.HOME_TBODY);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1685,7 +1714,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentLiveTimeSeconds > liveTotalTime) {
|
if (currentLiveTimeSeconds > liveTotalTime) {
|
||||||
setTimeout(() => {
|
timeoutRef.current = setTimeout(() => {
|
||||||
dispatch(getMainLiveShow());
|
dispatch(getMainLiveShow());
|
||||||
setShopNowInfo('');
|
setShopNowInfo('');
|
||||||
dispatch(
|
dispatch(
|
||||||
@@ -1694,8 +1723,21 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}, 3000);
|
}, 3000);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}, [currentLiveTimeSeconds, liveTotalTime]);
|
}, [currentLiveTimeSeconds, liveTotalTime, dispatch, playListInfo, selectedIndex]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
||||||
|
if (watchIntervalLive.current) clearInterval(watchIntervalLive.current);
|
||||||
|
if (watchIntervalVod.current) clearInterval(watchIntervalVod.current);
|
||||||
|
if (watchIntervalMedia.current) clearInterval(watchIntervalMedia.current);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const mediainfoHandler = useCallback(
|
const mediainfoHandler = useCallback(
|
||||||
(ev) => {
|
(ev) => {
|
||||||
@@ -1719,6 +1761,24 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
setCurrentTime(videoPlayer.current?.getMediaState()?.currentTime);
|
setCurrentTime(videoPlayer.current?.getMediaState()?.currentTime);
|
||||||
break;
|
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': {
|
case 'error': {
|
||||||
dispatch(
|
dispatch(
|
||||||
sendBroadCast({
|
sendBroadCast({
|
||||||
@@ -1732,6 +1792,23 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
const mediaId = videoPlayer.current?.video?.media?.mediaId;
|
const mediaId = videoPlayer.current?.video?.media?.mediaId;
|
||||||
setMediaId(mediaId);
|
setMediaId(mediaId);
|
||||||
setVideoLoaded(true);
|
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(
|
dlog(
|
||||||
'[PlayerPanel] 🎬 Video Loaded - shptmBanrTpNm:',
|
'[PlayerPanel] 🎬 Video Loaded - shptmBanrTpNm:',
|
||||||
panelInfoRef.current?.shptmBanrTpNm
|
panelInfoRef.current?.shptmBanrTpNm
|
||||||
@@ -1984,6 +2061,17 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
setVideoLoaded(false);
|
setVideoLoaded(false);
|
||||||
}, [currentPlayingUrl]);
|
}, [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(() => {
|
useEffect(() => {
|
||||||
if (currentPlayingUrl) {
|
if (currentPlayingUrl) {
|
||||||
@@ -1994,7 +2082,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
|
|
||||||
const handlePopupClose = useCallback(() => {
|
const handlePopupClose = useCallback(() => {
|
||||||
dispatch(setHidePopup());
|
dispatch(setHidePopup());
|
||||||
setTimeout(() => Spotlight.focus(SpotlightIds.PLAYER_SUBTITLE_BUTTON));
|
timeoutRef.current = setTimeout(() => Spotlight.focus(SpotlightIds.PLAYER_SUBTITLE_BUTTON));
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
const reactPlayerSubtitleConfig = useMemo(() => {
|
const reactPlayerSubtitleConfig = useMemo(() => {
|
||||||
if (isSubtitleActive && currentSubtitleBlob) {
|
if (isSubtitleActive && currentSubtitleBlob) {
|
||||||
@@ -2324,7 +2412,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
Spotlight.pause();
|
Spotlight.pause();
|
||||||
setTimeout(() => {
|
timeoutRef.current = setTimeout(() => {
|
||||||
Spotlight.resume();
|
Spotlight.resume();
|
||||||
dispatch(PanelActions.popPanel());
|
dispatch(PanelActions.popPanel());
|
||||||
}, VIDEO_END_ACTION_DELAY);
|
}, VIDEO_END_ACTION_DELAY);
|
||||||
@@ -2332,7 +2420,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
}
|
}
|
||||||
if (panelInfoRef.current.shptmBanrTpNm === 'VOD') {
|
if (panelInfoRef.current.shptmBanrTpNm === 'VOD') {
|
||||||
Spotlight.pause();
|
Spotlight.pause();
|
||||||
setTimeout(() => {
|
timeoutRef.current = setTimeout(() => {
|
||||||
stopExternalPlayer();
|
stopExternalPlayer();
|
||||||
if (panelInfoRef.current.modal) {
|
if (panelInfoRef.current.modal) {
|
||||||
// 모달 모드에서는 종료 후 화면을 유지하고 Back 아이콘으로 포커스 이동
|
// 모달 모드에서는 종료 후 화면을 유지하고 Back 아이콘으로 포커스 이동
|
||||||
@@ -2582,7 +2670,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
|
|
||||||
if (lastFocusedTargetId) {
|
if (lastFocusedTargetId) {
|
||||||
// ShopNowContents가 렌더링될 때까지 잠시 대기 후 포커스 복원
|
// ShopNowContents가 렌더링될 때까지 잠시 대기 후 포커스 복원
|
||||||
setTimeout(() => {
|
timeoutRef.current = setTimeout(() => {
|
||||||
dlog('[PlayerPanel] 🔍 500ms 후 포커스 복원 시도:', lastFocusedTargetId);
|
dlog('[PlayerPanel] 🔍 500ms 후 포커스 복원 시도:', lastFocusedTargetId);
|
||||||
Spotlight.focus(lastFocusedTargetId);
|
Spotlight.focus(lastFocusedTargetId);
|
||||||
}, 500);
|
}, 500);
|
||||||
@@ -2591,6 +2679,10 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
// 한 번 처리한 복귀 플래그는 즉시 해제해 중복 영향을 막는다.
|
// 한 번 처리한 복귀 플래그는 즉시 해제해 중복 영향을 막는다.
|
||||||
prevIsTopPanelDetailFromPlayerRef.current = false;
|
prevIsTopPanelDetailFromPlayerRef.current = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
isOnTop,
|
isOnTop,
|
||||||
|
|||||||
Reference in New Issue
Block a user