6 Commits

Author SHA1 Message Date
a539e2617e [251126] fix: Memory Monitoring - mediaMemory
🕐 커밋 시간: 2025. 11. 26. 09:19:24

📊 변경 통계:
  • 총 파일: 1개
  • 추가: +464줄
  • 삭제: -2줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/utils/memoryMonitor.js

🔧 주요 변경 내용:
  • 공통 유틸리티 함수 최적화
  • 대규모 기능 개발
2025-11-26 09:19:24 +09:00
a9fd3981c8 [251125] fix: Memory Monitoring - 3
🕐 커밋 시간: 2025. 11. 25. 23:08:27

📊 변경 통계:
  • 총 파일: 2개
  • 추가: +54줄
  • 삭제: -2줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/utils/memoryMonitor.js
  ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx

🔧 주요 변경 내용:
  • 공통 유틸리티 함수 최적화
  • 소규모 기능 개선
2025-11-25 23:08:28 +09:00
6d345ddddc [251125] fix: Memory Monitoring - 2
🕐 커밋 시간: 2025. 11. 25. 23:01:53

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

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx

🔧 주요 변경 내용:
  • 코드 정리 및 최적화

Performance: 코드 최적화로 성능 개선 기대
2025-11-25 23:01:53 +09:00
943be925a8 [251125] fix: Memory Monitoring - 1
🕐 커밋 시간: 2025. 11. 25. 22:58:25

📊 변경 통계:
  • 총 파일: 2개
  • 추가: +20줄
  • 삭제: -26줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.js
  ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx

🔧 주요 변경 내용:
  • UI 컴포넌트 아키텍처 개선
  • 코드 정리 및 최적화

Performance: 코드 최적화로 성능 개선 기대
2025-11-25 22:58:25 +09:00
c88c0cebc8 [251125] memory monitoring 2025-11-25 22:47:00 +09:00
7baeca9432 [251125] fix: PlayerPanel-VidoerPlayer최적화-1
🕐 커밋 시간: 2025. 11. 25. 22:28:43

📊 변경 통계:
  • 총 파일: 2개
  • 추가: +30줄
  • 삭제: -10줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.js
  ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx

🔧 주요 변경 내용:
  • UI 컴포넌트 아키텍처 개선
  • 소규모 기능 개선
2025-11-25 22:28:43 +09:00
3 changed files with 830 additions and 10 deletions

View File

@@ -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);
}); });
} }

View 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;

View File

@@ -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,