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 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');
|
||||
@@ -776,6 +778,7 @@ const VideoPlayerBase = class extends React.Component {
|
||||
this.sliderKnobProportion = 0;
|
||||
this.mediaControlsSpotlightId = props.spotlightId + '_mediaControls';
|
||||
this.jumpButtonPressed = null;
|
||||
this.focusTimer = null;
|
||||
|
||||
// Re-render-necessary State
|
||||
this.state = {
|
||||
@@ -825,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);
|
||||
@@ -1020,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) {
|
||||
@@ -1038,6 +1043,7 @@ const VideoPlayerBase = class extends React.Component {
|
||||
this.stopDelayedTitleHide();
|
||||
this.stopDelayedFeedbackHide();
|
||||
this.stopDelayedMiniFeedbackHide();
|
||||
if (this.focusTimer) clearTimeout(this.focusTimer);
|
||||
this.announceJob.stop();
|
||||
this.renderBottomControl.stop();
|
||||
this.slider5WayPressJob.stop();
|
||||
@@ -1126,6 +1132,7 @@ const VideoPlayerBase = class extends React.Component {
|
||||
}
|
||||
// 레퍼런스도 해제해 GC 대상이 되도록 함
|
||||
this.video = null;
|
||||
memoryMonitor.logMemory('[VideoPlayer] componentWillUnmount - cleanup done');
|
||||
// console.log('[VideoPlayer] componentWillUnmount - cleanup done', { src: this.props?.src });
|
||||
if (this.floatingLayerController) {
|
||||
this.floatingLayerController.unregister();
|
||||
@@ -1562,6 +1569,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,
|
||||
@@ -1796,6 +1811,10 @@ const VideoPlayerBase = class extends React.Component {
|
||||
* @public
|
||||
*/
|
||||
play = () => {
|
||||
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,
|
||||
@@ -1840,6 +1859,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,
|
||||
@@ -2603,11 +2626,11 @@ const VideoPlayerBase = class extends React.Component {
|
||||
this.showControls();
|
||||
|
||||
if (this.state.lastFocusedTarget) {
|
||||
setTimeout(() => {
|
||||
this.focusTimer = setTimeout(() => {
|
||||
Spotlight.focus(this.state.lastFocusedTarget);
|
||||
});
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
this.focusTimer = setTimeout(() => {
|
||||
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 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);
|
||||
@@ -282,6 +284,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
const watchIntervalLive = useRef(null);
|
||||
const watchIntervalVod = useRef(null);
|
||||
const watchIntervalMedia = useRef(null);
|
||||
const timeoutRef = useRef(null);
|
||||
// useEffect(() => {
|
||||
// console.log("###videoLoaded", videoLoaded);
|
||||
// if (nowMenu) {
|
||||
@@ -369,6 +372,32 @@ 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');
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 재생 중 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 감지 추가
|
||||
useEffect(() => {
|
||||
dlog('[PlayerPanel] 🔍 isOnTop useEffect 호출:', {
|
||||
@@ -619,7 +648,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
|
||||
if (lastFocusedTargetId) {
|
||||
// ShopNowContents가 렌더링될 때까지 대기 후 포커스 복원
|
||||
setTimeout(() => {
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
dlog('[PlayerPanel] 🔍 800ms 후 포커스 복원 시도:', lastFocusedTargetId);
|
||||
Spotlight.focus(lastFocusedTargetId);
|
||||
}, 800);
|
||||
@@ -1147,7 +1176,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
|
||||
//딮링크로 플레이어 진입 후 이전버튼 클릭시
|
||||
if (panels.length === 1) {
|
||||
setTimeout(() => {
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
Spotlight.focus(SpotlightIds.HOME_TBODY);
|
||||
});
|
||||
}
|
||||
@@ -1685,7 +1714,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
|
||||
useEffect(() => {
|
||||
if (currentLiveTimeSeconds > liveTotalTime) {
|
||||
setTimeout(() => {
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
dispatch(getMainLiveShow());
|
||||
setShopNowInfo('');
|
||||
dispatch(
|
||||
@@ -1694,8 +1723,21 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
})
|
||||
);
|
||||
}, 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(
|
||||
(ev) => {
|
||||
@@ -1719,6 +1761,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({
|
||||
@@ -1732,6 +1792,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
|
||||
@@ -1984,6 +2061,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) {
|
||||
@@ -1994,7 +2082,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
|
||||
const handlePopupClose = useCallback(() => {
|
||||
dispatch(setHidePopup());
|
||||
setTimeout(() => Spotlight.focus(SpotlightIds.PLAYER_SUBTITLE_BUTTON));
|
||||
timeoutRef.current = setTimeout(() => Spotlight.focus(SpotlightIds.PLAYER_SUBTITLE_BUTTON));
|
||||
}, [dispatch]);
|
||||
const reactPlayerSubtitleConfig = useMemo(() => {
|
||||
if (isSubtitleActive && currentSubtitleBlob) {
|
||||
@@ -2324,7 +2412,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
})
|
||||
);
|
||||
Spotlight.pause();
|
||||
setTimeout(() => {
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
Spotlight.resume();
|
||||
dispatch(PanelActions.popPanel());
|
||||
}, VIDEO_END_ACTION_DELAY);
|
||||
@@ -2332,7 +2420,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
}
|
||||
if (panelInfoRef.current.shptmBanrTpNm === 'VOD') {
|
||||
Spotlight.pause();
|
||||
setTimeout(() => {
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
stopExternalPlayer();
|
||||
if (panelInfoRef.current.modal) {
|
||||
// 모달 모드에서는 종료 후 화면을 유지하고 Back 아이콘으로 포커스 이동
|
||||
@@ -2582,7 +2670,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
|
||||
if (lastFocusedTargetId) {
|
||||
// ShopNowContents가 렌더링될 때까지 잠시 대기 후 포커스 복원
|
||||
setTimeout(() => {
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
dlog('[PlayerPanel] 🔍 500ms 후 포커스 복원 시도:', lastFocusedTargetId);
|
||||
Spotlight.focus(lastFocusedTargetId);
|
||||
}, 500);
|
||||
@@ -2591,6 +2679,10 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
// 한 번 처리한 복귀 플래그는 즉시 해제해 중복 영향을 막는다.
|
||||
prevIsTopPanelDetailFromPlayerRef.current = false;
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
||||
};
|
||||
}
|
||||
}, [
|
||||
isOnTop,
|
||||
|
||||
Reference in New Issue
Block a user