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

🕐 커밋 시간: 2025. 11. 24. 19:23:39

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

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/actions/playActions.js
  ~ com.twin.app.shoptime/src/components/MediaItem/MediaItem.js
  ~ com.twin.app.shoptime/src/components/VideoPlayer/MediaTitle.js
  ~ com.twin.app.shoptime/src/components/VideoPlayer/TReactPlayer.jsx
  ~ com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.js
  ~ com.twin.app.shoptime/src/hooks/useReviews/useReviews.js
  ~ com.twin.app.shoptime/src/utils/helperMethods.js
  ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
  • UI 컴포넌트 아키텍처 개선
  • 공통 유틸리티 함수 최적화
  • 중간 규모 기능 개선
  • 모듈 구조 개선
This commit is contained in:
2025-11-24 19:23:41 +09:00
parent 9674448865
commit 7da55ea1ae
8 changed files with 141 additions and 30 deletions

View File

@@ -1147,3 +1147,113 @@ export const setDisplayFullscreen = () => ({
lastUpdate: Date.now(), lastUpdate: Date.now(),
}, },
}); });
/**
* 배너 비디오를 시작합니다.
* @param {Object} videoInfo - 비디오 정보
* @param {string} videoInfo.bannerId - 배너 ID
* @param {string} videoInfo.videoId - 비디오 ID
* @param {string} videoInfo.showUrl - 비디오 URL
* @param {boolean} videoInfo.modal - 모달 여부
* @param {string} videoInfo.modalContainerId - 모달 컨테이너 ID
* @param {string} videoInfo.modalClassName - 모달 클래스 이름
*/
export const startBannerVideo = (videoInfo) => (dispatch, getState) => {
dlog('[startBannerVideo] ✅ START - videoInfo:', videoInfo);
const {
bannerId,
videoId,
showUrl,
modal = true,
modalContainerId,
modalClassName,
...rest
} = videoInfo;
// 비디오 식별자 생성
const videoIdentifier = videoId || showUrl || bannerId;
if (videoIdentifier) {
const displayMode = modal ? DISPLAY_STATUS.VISIBLE : DISPLAY_STATUS.FULLSCREEN;
dlog(
'[startBannerVideo] 📌 Setting playback loading - identifier:',
videoIdentifier,
', displayMode:',
displayMode
);
dispatch(setPlaybackLoading(videoIdentifier, displayMode));
}
const panels = getState().panels.panels;
const existingPlayerPanel = panels.find((p) => p.name === panel_names.PLAYER_PANEL);
// 기존 PlayerPanel이 있으면 초기화
if (existingPlayerPanel) {
dlog('[startBannerVideo] 🔄 Resetting existing PLAYER_PANEL before start');
clearAllVideoTimers();
dispatch(popPanel(panel_names.PLAYER_PANEL));
}
// 새로운 PlayerPanel push
dispatch(
pushPanel(
{
name: panel_names.PLAYER_PANEL,
panelInfo: {
modal,
modalContainerId,
modalClassName,
playerState: {
currentBannerId: bannerId,
},
videoId,
showUrl,
bannerId,
...rest,
},
},
true
)
);
dlog('[startBannerVideo] ✨ Panel action dispatched');
};
/**
* 비디오를 중지하고 화면에서 숨깁니다.
* 패널을 닫지 않고 비디오 재생만 중지합니다.
*/
export const stopAndHideVideo = () => (dispatch, getState) => {
const panels = getState().panels.panels;
// 모든 PlayerPanel 찾기
const playerPanels = panels.filter((panel) => panel.name === panel_names.PLAYER_PANEL);
if (playerPanels.length > 0) {
dlog('[stopAndHideVideo] Stopping all video playback and hiding');
// 타이머 정리
if (startVideoFocusTimer) {
clearTimeout(startVideoFocusTimer);
startVideoFocusTimer = null;
}
// 모든 PlayerPanel을 중지 및 숨김 상태로 업데이트
playerPanels.forEach((playerPanel) => {
dispatch(
updatePanel({
name: panel_names.PLAYER_PANEL,
panelInfo: {
...playerPanel.panelInfo,
shouldStop: true,
isPaused: true,
isHidden: true,
},
})
);
});
// Redux 상태도 중지로 업데이트
dispatch(setVideoStopped());
}
};

View File

@@ -20,7 +20,7 @@ import * as Config from "../../utils/Config";
import * as ContentType from "../../utils/Config"; import * as ContentType from "../../utils/Config";
import * as Utils from "../../utils/helperMethods"; import * as Utils from "../../utils/helperMethods";
import { $L } from "../../utils/helperMethods"; import { $L } from "../../utils/helperMethods";
import SpotlightIds from "../../utils/SpotlightIds"; import { SpotlightIds } from "../../utils/SpotlightIds";
import css from "./MediaItem.module.less"; import css from "./MediaItem.module.less";
/** /**

View File

@@ -10,7 +10,7 @@ import { SpotlightContainerDecorator } from "@enact/spotlight/SpotlightContainer
import ForwardRef from "@enact/ui/ForwardRef"; import ForwardRef from "@enact/ui/ForwardRef";
import { $L } from "../../utils/helperMethods"; import { $L } from "../../utils/helperMethods";
import SpotlightIds from "../../utils/SpotlightIds"; import { SpotlightIds } from "../../utils/SpotlightIds";
import TButton from "../TButton/TButton"; import TButton from "../TButton/TButton";
import css from "./MediaTitle.module.less"; import css from "./MediaTitle.module.less";

View File

@@ -158,7 +158,7 @@ export default function TReactPlayer({
// 🔽 [최적화] URL 변경 또는 언마운트 시 이전 비디오 정리 (메모리 누수 방지) // 🔽 [최적화] URL 변경 또는 언마운트 시 이전 비디오 정리 (메모리 누수 방지)
useEffect(() => { useEffect(() => {
return () => { return () => {
console.log('[TReactPlayer] cleanup - start', { url }); // console.log('[TReactPlayer] cleanup - start', { url });
const videoNode = playerRef.current?.getInternalPlayer(); const videoNode = playerRef.current?.getInternalPlayer();
if (videoNode) { if (videoNode) {
try { try {
@@ -179,7 +179,7 @@ export default function TReactPlayer({
console.warn('[TReactPlayer] cleanup warning:', err); console.warn('[TReactPlayer] cleanup warning:', err);
} }
} }
console.log('[TReactPlayer] cleanup - done', { url }); // console.log('[TReactPlayer] cleanup - done', { url });
}; };
}, [url]); // ✅ URL 변경 시에도 정리 로직 실행 }, [url]); // ✅ URL 변경 시에도 정리 로직 실행

View File

@@ -1020,7 +1020,7 @@ const VideoPlayerBase = class extends React.Component {
} }
componentWillUnmount() { componentWillUnmount() {
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) {
off('touchmove', this.activityDetected); off('touchmove', this.activityDetected);
@@ -1089,7 +1089,7 @@ const VideoPlayerBase = class extends React.Component {
// 정리 중 에러는 무시하고 언마운트 진행 // 정리 중 에러는 무시하고 언마운트 진행
// console.warn('[VideoPlayer] cleanup error', err); // console.warn('[VideoPlayer] cleanup error', err);
} }
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();
} }

View File

@@ -1,6 +1,6 @@
import { useState, useMemo, useEffect, useCallback } from 'react'; import { useState, useMemo, useEffect, useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux'; import { useSelector, useDispatch } from 'react-redux';
import { getUserReviews, getUserReviewList, getReviewFilters, clearReviewFilter } from '../../actions/productActions'; import { getUserReviewList, getReviewFilters, clearReviewFilter } from '../../actions/productActions';
import fp from '../../utils/fp'; import fp from '../../utils/fp';
const DISPLAY_SIZE = 3; // 화면에 표시할 리뷰 개수 const DISPLAY_SIZE = 3; // 화면에 표시할 리뷰 개수
@@ -175,28 +175,22 @@ const useReviews = (prdtId, patnrId, _deprecatedReviewVersion) => {
// }); // });
try { try {
if (reviewVersion === 1) { // 신 API 호출 (v2)
// 기존 API 호출 // console.log('[useReviews] 🔄 getUserReviewList 호출 중... (v2)');
// console.log('[useReviews] 🔄 getUserReviews 호출 중... (v1)'); await dispatch(getUserReviewList({
await dispatch(getUserReviews({ prdtId, patnrId })); prdtId,
} else { patnrId,
// 신 API 호출 (v2) filterTpCd: 'ALL',
// console.log('[useReviews] 🔄 getUserReviewList 호출 중... (v2)'); pageSize: 100,
await dispatch(getUserReviewList({ pageNo: 1
prdtId, }));
patnrId,
filterTpCd: 'ALL',
pageSize: 100,
pageNo: 1
}));
// IF-LGSP-100 필터 데이터 조회 // IF-LGSP-100 필터 데이터 조회
// console.log('[useReviews] 🔄 getReviewFilters 호출 중... (IF-LGSP-100)'); // console.log('[useReviews] 🔄 getReviewFilters 호출 중... (IF-LGSP-100)');
await dispatch(getReviewFilters({ await dispatch(getReviewFilters({
prdtId, prdtId,
patnrId patnrId
})); }));
}
setHasLoadedData(true); setHasLoadedData(true);
} catch (error) { } catch (error) {
console.error('[useReviews] loadReviews 실패:', error); console.error('[useReviews] loadReviews 실패:', error);

View File

@@ -572,3 +572,10 @@ export const getReservationErrorMessage = (error) => {
return $L("Failed to set reminder. Please try again."); return $L("Failed to set reminder. Please try again.");
}; };
export const convertNewlinesToBr = (text) => {
if (typeof text !== 'string') {
return text;
}
return text.replace(/\n/g, '<br>');
};

View File

@@ -2222,13 +2222,13 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
useEffect(() => { useEffect(() => {
return () => { return () => {
console.log('[PlayerPanel] unmount cleanup start'); // console.log('[PlayerPanel] unmount cleanup start');
cleanupPlayerOnUnmount(); cleanupPlayerOnUnmount();
stopExternalPlayer(); stopExternalPlayer();
dispatch(clearShopNowInfo()); dispatch(clearShopNowInfo());
dispatch(CLEAR_PLAYER_INFO()); dispatch(CLEAR_PLAYER_INFO());
setShopNowInfo([]); setShopNowInfo([]);
console.log('[PlayerPanel] unmount cleanup done'); // console.log('[PlayerPanel] unmount cleanup done');
}; };
}, [cleanupPlayerOnUnmount, stopExternalPlayer, dispatch]); }, [cleanupPlayerOnUnmount, stopExternalPlayer, dispatch]);