fix: resolve merge conflict in ProductVideo.v2

Resolved stash pop merge conflict by keeping the cleaner stashed version.
- Simplified VideoPlayer wrapper structure
- Maintained PalmSystem source/track elements

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-15 21:06:06 +09:00
parent 0994ca98a8
commit cdfb215255
2 changed files with 174 additions and 79 deletions

View File

@@ -132,3 +132,26 @@
// PlayerPanel 모달에서 사용되는 스타일
z-index: 1000;
}
// 일반 재생 모드 container (VideoPlayer가 초기 렌더링되는 위치)
.normalPlayerContainer {
width: 100%;
height: 100%;
}
// 전체화면 container (Portal, body에 항상 존재)
.fullscreenContainer {
position: fixed;
top: 0;
left: 0;
width: 1920px;
height: 1080px;
z-index: 99999;
background-color: @COLOR_BLACK;
.videoPlayerWrapper {
width: 100%;
height: 100%;
border-radius: 0;
}
}

View File

@@ -52,6 +52,12 @@ export default function ProductVideoV2({
const autoPlayTimerRef = useRef(null);
const containerRef = useRef(null);
// VideoPlayer DOM 이동을 위한 refs
const videoPlayerWrapperRef = useRef(null);
const normalContainerRef = useRef(null);
const fullscreenContainerRef = useRef(null);
const initialRenderContainerRef = useRef(null); // React가 처음 렌더링하는 위치
// 비디오 재생 가능 여부 체크
const canPlayVideo = useMemo(() => {
return Boolean(productInfo?.prdtMediaUrl);
@@ -230,6 +236,42 @@ export default function ProductVideoV2({
};
}, [autoPlay, canPlayVideo, isPlaying]);
// VideoPlayer wrapper를 적절한 container로 이동
useEffect(() => {
if (!isPlaying) return;
const wrapper = videoPlayerWrapperRef.current;
const normalContainer = normalContainerRef.current;
const fullscreenContainer = fullscreenContainerRef.current;
if (!wrapper || !normalContainer || !fullscreenContainer) {
console.warn('[ProductVideoV2] Refs not ready:', {
wrapper: !!wrapper,
normalContainer: !!normalContainer,
fullscreenContainer: !!fullscreenContainer,
});
return;
}
// 처음 재생 시작 시: 임시 컨테이너에서 적절한 위치로 이동
// 이후 전환 시: 컨테이너 간 이동
if (isFullscreen) {
// 전체화면: wrapper를 fullscreen container로 이동
if (wrapper.parentElement !== fullscreenContainer) {
fullscreenContainer.appendChild(wrapper);
fullscreenContainer.style.display = 'block';
console.log('[ProductVideoV2] Moved to fullscreen');
}
} else {
// 일반 모드: wrapper를 normal container로 이동
if (wrapper.parentElement !== normalContainer) {
normalContainer.appendChild(wrapper);
fullscreenContainer.style.display = 'none';
console.log('[ProductVideoV2] Moved to normal');
}
}
}, [isFullscreen, isPlaying]);
if (!canPlayVideo) return null;
// 컨테이너 컴포넌트 결정
@@ -258,87 +300,117 @@ export default function ProductVideoV2({
}
: {};
// 비디오 플레이어 컨텐츠
const videoContent = (
<ContainerComponent
{...containerProps}
ref={containerRef}
className={`${css.videoContainer} ${isFullscreen ? css.fullscreen : ''}`}
// VideoPlayer 컴포넌트 (한 번만 생성, DOM 이동으로 위치 변경)
const videoPlayerElement = isPlaying ? (
<div
ref={videoPlayerWrapperRef}
className={`${css.videoPlayerWrapper} ${isFullscreen ? css.fullscreenPlayer : ''}`}
>
{!isPlaying ? (
// 썸네일 + 재생 버튼 표시
<SpottableComponent
className={css.videoThumbnailContainer}
onClick={handleThumbnailClick}
onFocus={videoContainerOnFocus}
onBlur={videoContainerOnBlur}
onSpotlightDown={handleSpotlightDown}
spotlightId="product-video-v2-thumbnail"
aria-label={`${productInfo?.prdtNm} 동영상 재생`}
>
<div className={css.videoThumbnailWrapper}>
<CustomImage
src={thumbnailUrl}
alt={`${productInfo?.prdtNm} 동영상 썸네일`}
className={css.videoThumbnail}
/>
<div className={css.playButtonOverlay}>
<img src={playImg} alt="재생" />
</div>
</div>
</SpottableComponent>
) : (
// VideoPlayer 내장 표시
<div className={`${css.videoPlayerWrapper} ${isFullscreen ? css.fullscreenPlayer : ''}`}>
<VideoPlayer
setApiProvider={getPlayer}
disabled={false}
onEnded={handleVideoEnded}
onBackButton={handleBackButton}
noAutoPlay={false}
autoCloseTimeout={3000}
spotlightDisabled={!isFullscreen}
isYoutube={isYoutube}
src={productInfo?.prdtMediaUrl}
reactPlayerConfig={reactPlayerSubtitleConfig}
thumbnailUrl={thumbnailUrl}
title={productInfo?.prdtNm}
videoComponent={
(typeof window === 'object' && !window.PalmSystem) || isYoutube ? TReactPlayer : Media
}
type="MEDIA"
panelInfo={{ modal: false }}
captionEnable={false}
setIsSubtitleActive={setIsSubtitleActive}
setCurrentTime={setCurrentTime}
setIsVODPaused={setIsVODPaused}
playListInfo={[]}
selectedIndex={0}
videoVerticalVisible={false}
sideContentsVisible={false}
setSideContentsVisible={setSideContentsVisible}
handleIndicatorDownClick={handleIndicatorDownClick}
handleIndicatorUpClick={handleIndicatorUpClick}
<VideoPlayer
setApiProvider={getPlayer}
disabled={false}
onEnded={handleVideoEnded}
onBackButton={handleBackButton}
noAutoPlay={false}
autoCloseTimeout={3000}
spotlightDisabled={!isFullscreen}
isYoutube={isYoutube}
src={productInfo?.prdtMediaUrl}
reactPlayerConfig={reactPlayerSubtitleConfig}
thumbnailUrl={thumbnailUrl}
title={productInfo?.prdtNm}
videoComponent={
(typeof window === 'object' && !window.PalmSystem) || isYoutube ? TReactPlayer : Media
}
type="MEDIA"
panelInfo={{ modal: false }}
captionEnable={false}
setIsSubtitleActive={setIsSubtitleActive}
setCurrentTime={setCurrentTime}
setIsVODPaused={setIsVODPaused}
playListInfo={[]}
selectedIndex={0}
videoVerticalVisible={false}
sideContentsVisible={false}
setSideContentsVisible={setSideContentsVisible}
handleIndicatorDownClick={handleIndicatorDownClick}
handleIndicatorUpClick={handleIndicatorUpClick}
>
{typeof window === 'object' && window.PalmSystem && (
<source src={productInfo?.prdtMediaUrl} type={videoType} />
)}
{productInfo?.prdtMediaSubtitlUrl &&
typeof window === 'object' &&
window.PalmSystem && (
<track kind="subtitles" src={productInfo?.prdtMediaSubtitlUrl} default />
)}
</VideoPlayer>
</div>
) : null;
return (
<>
{/* 일반 모드 container */}
<ContainerComponent
{...containerProps}
ref={containerRef}
className={`${css.videoContainer} ${isFullscreen ? css.fullscreen : ''}`}
>
{!isPlaying ? (
// 썸네일 + 재생 버튼 표시
<SpottableComponent
className={css.videoThumbnailContainer}
onClick={handleThumbnailClick}
onFocus={videoContainerOnFocus}
onBlur={videoContainerOnBlur}
spotlightId="product-video-v2-thumbnail"
aria-label={`${productInfo?.prdtNm} 동영상 재생`}
>
{typeof window === 'object' && window.PalmSystem && (
<source src={productInfo?.prdtMediaUrl} type={videoType} />
)}
{productInfo?.prdtMediaSubtitlUrl &&
typeof window === 'object' &&
window.PalmSystem && (
<track kind="subtitles" src={productInfo?.prdtMediaSubtitlUrl} default />
)}
</VideoPlayer>
</div>
<div className={css.videoThumbnailWrapper}>
<CustomImage
src={thumbnailUrl}
alt={`${productInfo?.prdtNm} 동영상 썸네일`}
className={css.videoThumbnail}
/>
<div className={css.playButtonOverlay}>
<img src={playImg} alt="재생" />
</div>
</div>
</SpottableComponent>
) : (
// 일반 재생 모드: VideoPlayer가 여기에 초기 렌더링됨
<div ref={normalContainerRef} className={css.normalPlayerContainer} />
)}
</ContainerComponent>
{/* 전체화면 container (Portal, 항상 body에 존재) */}
{ReactDOM.createPortal(
<SpotlightContainer
spotlightRestrict="self-only"
spotlightId="product-video-v2-fullscreen-portal"
>
<div
ref={fullscreenContainerRef}
className={css.fullscreenContainer}
style={{ display: 'none' }}
/>
</SpotlightContainer>,
document.body
)}
</ContainerComponent>
{/* 임시 렌더링 컨테이너 - React가 여기에 VideoPlayer 렌더링, useEffect가 실제 위치로 이동 */}
<div
ref={initialRenderContainerRef}
style={{
position: 'absolute',
top: -9999,
left: -9999,
visibility: 'hidden',
pointerEvents: 'none',
}}
>
{videoPlayerElement}
</div>
</>
);
// 전체화면일 때는 Portal로 body에 직접 렌더링
if (isFullscreen) {
return ReactDOM.createPortal(videoContent, document.body);
}
// 일반 모드일 때는 그냥 렌더링
return videoContent;
}