[251113] feat: MediaPanel ref Video Control
🕐 커밋 시간: 2025. 11. 13. 20:51:15 📊 변경 통계: • 총 파일: 1개 • 추가: +41줄 • 삭제: -3줄 📝 수정된 파일: ~ com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.jsx 🔧 함수 변경 내용: 📄 com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.jsx (javascript): ❌ Deleted: MediaPanel()
This commit is contained in:
@@ -1,4 +1,11 @@
|
|||||||
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react';
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useImperativeHandle,
|
||||||
|
useLayoutEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
@@ -56,498 +63,542 @@ const YOUTUBECONFIG = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const MediaPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props }) => {
|
const MediaPanel = React.forwardRef(
|
||||||
const dispatch = useDispatch();
|
({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props }, ref) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const videoPlayer = useRef(null);
|
const videoPlayer = useRef(null);
|
||||||
const onEndedTimerRef = useRef(null); // ✅ onEnded 타이머 관리
|
const onEndedTimerRef = useRef(null); // ✅ onEnded 타이머 관리
|
||||||
const mediaEventListenersRef = useRef([]); // ✅ 미디어 이벤트 리스너 추적
|
const mediaEventListenersRef = useRef([]); // ✅ 미디어 이벤트 리스너 추적
|
||||||
const [modalStyle, setModalStyle] = React.useState({});
|
const [modalStyle, setModalStyle] = React.useState({});
|
||||||
const [modalScale, setModalScale] = React.useState(1);
|
const [modalScale, setModalScale] = React.useState(1);
|
||||||
const [currentTime, setCurrentTime] = React.useState(0);
|
const [currentTime, setCurrentTime] = React.useState(0);
|
||||||
const [videoLoaded, setVideoLoaded] = React.useState(false);
|
const [videoLoaded, setVideoLoaded] = React.useState(false);
|
||||||
const [isSubtitleActive, setIsSubtitleActive] = React.useState(true);
|
const [isSubtitleActive, setIsSubtitleActive] = React.useState(true);
|
||||||
|
|
||||||
const panelInfoRef = usePrevious(panelInfo);
|
const panelInfoRef = usePrevious(panelInfo);
|
||||||
|
|
||||||
// modal/full screen에 따른 일시정지/재생 처리
|
// modal/full screen에 따른 일시정지/재생 처리
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// debugLog('[MediaPanel] ========== isOnTop useEffect ==========');
|
// debugLog('[MediaPanel] ========== isOnTop useEffect ==========');
|
||||||
// debugLog('[MediaPanel] isOnTop:', isOnTop);
|
// debugLog('[MediaPanel] isOnTop:', isOnTop);
|
||||||
// debugLog('[MediaPanel] panelInfo:', JSON.stringify(panelInfo, null, 2));
|
// debugLog('[MediaPanel] panelInfo:', JSON.stringify(panelInfo, null, 2));
|
||||||
|
|
||||||
if (panelInfo && panelInfo.modal) {
|
if (panelInfo && panelInfo.modal) {
|
||||||
if (!isOnTop) {
|
if (!isOnTop) {
|
||||||
// debugLog('[MediaPanel] Not on top - pausing video');
|
// debugLog('[MediaPanel] Not on top - pausing video');
|
||||||
dispatch(pauseModalMedia());
|
dispatch(pauseModalMedia());
|
||||||
} else if (isOnTop && panelInfo.isPaused) {
|
} else if (isOnTop && panelInfo.isPaused) {
|
||||||
// debugLog('[MediaPanel] Back on top - resuming video');
|
// debugLog('[MediaPanel] Back on top - resuming video');
|
||||||
dispatch(resumeModalMedia());
|
dispatch(resumeModalMedia());
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [isOnTop, panelInfo, dispatch]);
|
|
||||||
|
|
||||||
// videoPlayer ref를 통한 직접 제어
|
|
||||||
useEffect(() => {
|
|
||||||
if (panelInfo?.modal && videoPlayer.current) {
|
|
||||||
if (panelInfo.isPaused) {
|
|
||||||
// debugLog('[MediaPanel] Executing pause via videoPlayer.current');
|
|
||||||
videoPlayer.current.pause();
|
|
||||||
} else if (panelInfo.isPaused === false) {
|
|
||||||
// debugLog('[MediaPanel] Executing play via videoPlayer.current');
|
|
||||||
videoPlayer.current.play();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [panelInfo?.isPaused, panelInfo?.modal]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!videoPlayer.current) return;
|
|
||||||
if (!isYoutube) return;
|
|
||||||
if (panelInfo?.modal) return;
|
|
||||||
|
|
||||||
videoPlayer.current.showControls?.();
|
|
||||||
}, [isYoutube, panelInfo?.modal]);
|
|
||||||
|
|
||||||
const getPlayer = useCallback((ref) => {
|
|
||||||
videoPlayer.current = ref;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// ✅ 안전한 비디오 플레이어 메서드 호출
|
|
||||||
const safePlayerCall = useCallback((methodName, ...args) => {
|
|
||||||
if (videoPlayer.current && typeof videoPlayer.current[methodName] === 'function') {
|
|
||||||
try {
|
|
||||||
return videoPlayer.current[methodName](...args);
|
|
||||||
} catch (err) {
|
|
||||||
if (DEBUG_MODE) console.warn(`[MediaPanel] ${methodName} 호출 실패:`, err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// VideoPlayer가 MEDIA 타입에서 setIsVODPaused를 호출하므로 더미 함수 제공
|
|
||||||
const setIsVODPaused = useCallback(() => {
|
|
||||||
// MediaPanel에서는 VOD pause 상태 관리 불필요 (단순 재생만)
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// PlayerOverlayContents에서 필요한 더미 함수들
|
|
||||||
const setSideContentsVisible = useCallback(() => {
|
|
||||||
// MediaPanel에서는 사이드 컨텐츠 사용 안 함
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleIndicatorDownClick = useCallback(() => {
|
|
||||||
// MediaPanel에서는 indicator 사용 안 함
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleIndicatorUpClick = useCallback(() => {
|
|
||||||
// MediaPanel에서는 indicator 사용 안 함
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// ✅ modal 스타일 설정
|
|
||||||
useEffect(() => {
|
|
||||||
let resizeObserver = null;
|
|
||||||
|
|
||||||
if (panelInfo.modal && panelInfo.modalContainerId) {
|
|
||||||
// modal 모드: modalContainerId 기반으로 위치와 크기 계산
|
|
||||||
const node = document.querySelector(`[data-spotlight-id="${panelInfo.modalContainerId}"]`);
|
|
||||||
if (node) {
|
|
||||||
const { width, height, top, left } = node.getBoundingClientRect();
|
|
||||||
|
|
||||||
// ProductVideo의 padding(6px * 2)과 추가 여유를 고려하여 크기 조정
|
|
||||||
// 비디오가 오른쪽으로 넘치지 않도록 충분한 여유 확보
|
|
||||||
const paddingOffset = 6 * 2; // padding 양쪽
|
|
||||||
const extraMargin = 6 * 2; // 추가 여유 (포커스 테두리 + 비디오 비율 고려)
|
|
||||||
const totalOffset = paddingOffset + extraMargin; // 24px
|
|
||||||
|
|
||||||
const adjustedWidth = width - totalOffset;
|
|
||||||
const adjustedHeight = height - totalOffset;
|
|
||||||
const adjustedTop = top + totalOffset / 2;
|
|
||||||
const adjustedLeft = left + totalOffset / 2;
|
|
||||||
|
|
||||||
const style = {
|
|
||||||
width: adjustedWidth + 'px',
|
|
||||||
height: adjustedHeight + 'px',
|
|
||||||
maxWidth: adjustedWidth + 'px',
|
|
||||||
maxHeight: adjustedHeight + 'px',
|
|
||||||
top: adjustedTop + 'px',
|
|
||||||
left: adjustedLeft + 'px',
|
|
||||||
position: 'fixed',
|
|
||||||
overflow: 'hidden', // visible → hidden으로 변경하여 넘치는 부분 숨김
|
|
||||||
};
|
|
||||||
setModalStyle(style);
|
|
||||||
let scale = 1;
|
|
||||||
if (typeof window === 'object') {
|
|
||||||
scale = adjustedWidth / window.innerWidth;
|
|
||||||
setModalScale(scale);
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
setModalStyle(panelInfo.modalStyle || {});
|
|
||||||
setModalScale(panelInfo.modalScale || 1);
|
|
||||||
}
|
}
|
||||||
} else if (isOnTop && !panelInfo.modal && !panelInfo.isMinimized && videoPlayer.current) {
|
}, [isOnTop, panelInfo, dispatch]);
|
||||||
// ✅ 안전한 메서드 호출로 null/undefined 체크
|
|
||||||
const mediaState = safePlayerCall('getMediaState');
|
// videoPlayer ref를 통한 직접 제어
|
||||||
if (mediaState?.paused) {
|
useEffect(() => {
|
||||||
safePlayerCall('play');
|
if (panelInfo?.modal && videoPlayer.current) {
|
||||||
|
if (panelInfo.isPaused) {
|
||||||
|
// debugLog('[MediaPanel] Executing pause via videoPlayer.current');
|
||||||
|
videoPlayer.current.pause();
|
||||||
|
} else if (panelInfo.isPaused === false) {
|
||||||
|
// debugLog('[MediaPanel] Executing play via videoPlayer.current');
|
||||||
|
videoPlayer.current.play();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}, [panelInfo?.isPaused, panelInfo?.modal]);
|
||||||
|
|
||||||
const isControlsHidden =
|
useEffect(() => {
|
||||||
videoPlayer.current.areControlsVisible && !videoPlayer.current.areControlsVisible();
|
if (!videoPlayer.current) return;
|
||||||
if (isControlsHidden) {
|
if (!isYoutube) return;
|
||||||
safePlayerCall('showControls');
|
if (panelInfo?.modal) return;
|
||||||
|
|
||||||
|
videoPlayer.current.showControls?.();
|
||||||
|
}, [isYoutube, panelInfo?.modal]);
|
||||||
|
|
||||||
|
const getPlayer = useCallback((ref) => {
|
||||||
|
videoPlayer.current = ref;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// ✅ 안전한 비디오 플레이어 메서드 호출
|
||||||
|
const safePlayerCall = useCallback((methodName, ...args) => {
|
||||||
|
if (videoPlayer.current && typeof videoPlayer.current[methodName] === 'function') {
|
||||||
|
try {
|
||||||
|
return videoPlayer.current[methodName](...args);
|
||||||
|
} catch (err) {
|
||||||
|
if (DEBUG_MODE) console.warn(`[MediaPanel] ${methodName} 호출 실패:`, err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
return null;
|
||||||
|
}, []);
|
||||||
|
|
||||||
// ✅ cleanup: resize observer 정리
|
const playViaRef = useCallback(() => safePlayerCall('play'), [safePlayerCall]);
|
||||||
return () => {
|
const pauseViaRef = useCallback(() => safePlayerCall('pause'), [safePlayerCall]);
|
||||||
if (resizeObserver) {
|
const seekViaRef = useCallback(
|
||||||
resizeObserver.disconnect();
|
(seconds = 0) => safePlayerCall('seek', seconds),
|
||||||
}
|
[safePlayerCall]
|
||||||
};
|
);
|
||||||
}, [panelInfo, isOnTop]);
|
const showControlsViaRef = useCallback(() => safePlayerCall('showControls'), [safePlayerCall]);
|
||||||
|
const hideControlsViaRef = useCallback(() => safePlayerCall('hideControls'), [safePlayerCall]);
|
||||||
|
const toggleControlsViaRef = useCallback(
|
||||||
|
() => safePlayerCall('toggleControls'),
|
||||||
|
[safePlayerCall]
|
||||||
|
);
|
||||||
|
const getMediaStateViaRef = useCallback(
|
||||||
|
() => safePlayerCall('getMediaState'),
|
||||||
|
[safePlayerCall]
|
||||||
|
);
|
||||||
|
|
||||||
// ✅ 비디오 클릭 시 modal → fullscreen 전환 또는 controls 토글
|
useImperativeHandle(
|
||||||
const onVideoClick = useCallback(() => {
|
ref,
|
||||||
if (panelInfo.modal) {
|
() => ({
|
||||||
// debugLog('[MediaPanel] Video clicked - switching to fullscreen');
|
play: playViaRef,
|
||||||
dispatch(switchMediaToFullscreen());
|
pause: pauseViaRef,
|
||||||
} else {
|
seek: seekViaRef,
|
||||||
// 비디오 클릭 시 controls 토글
|
showControls: showControlsViaRef,
|
||||||
safePlayerCall('toggleControls');
|
hideControls: hideControlsViaRef,
|
||||||
}
|
toggleControls: toggleControlsViaRef,
|
||||||
}, [dispatch, panelInfo.modal, safePlayerCall]);
|
getMediaState: getMediaStateViaRef,
|
||||||
|
getInternalPlayer: () => videoPlayer.current,
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
playViaRef,
|
||||||
|
pauseViaRef,
|
||||||
|
seekViaRef,
|
||||||
|
showControlsViaRef,
|
||||||
|
hideControlsViaRef,
|
||||||
|
toggleControlsViaRef,
|
||||||
|
getMediaStateViaRef,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
const onClickBack = useCallback(
|
// VideoPlayer가 MEDIA 타입에서 setIsVODPaused를 호출하므로 더미 함수 제공
|
||||||
(ev) => {
|
const setIsVODPaused = useCallback(() => {
|
||||||
// ✅ 뒤로가기 시 비디오 정지
|
// MediaPanel에서는 VOD pause 상태 관리 불필요 (단순 재생만)
|
||||||
safePlayerCall('pause');
|
}, []);
|
||||||
|
|
||||||
// modal에서 full로 전환된 경우 다시 modal로 돌아감
|
// PlayerOverlayContents에서 필요한 더미 함수들
|
||||||
if (panelInfo.modalContainerId && !panelInfo.modal) {
|
const setSideContentsVisible = useCallback(() => {
|
||||||
// 다시 modal로 돌리는 로직은 startVideoPlayer 액션을 사용할 수도 있지만
|
// MediaPanel에서는 사이드 컨텐츠 사용 안 함
|
||||||
// 여기서는 단순히 패널을 pop
|
}, []);
|
||||||
dispatch(PanelActions.popPanel());
|
|
||||||
ev?.stopPropagation();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!panelInfo.modal) {
|
const handleIndicatorDownClick = useCallback(() => {
|
||||||
dispatch(PanelActions.popPanel());
|
// MediaPanel에서는 indicator 사용 안 함
|
||||||
ev?.stopPropagation();
|
}, []);
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[dispatch, panelInfo, safePlayerCall]
|
|
||||||
);
|
|
||||||
|
|
||||||
const currentPlayingUrl = useMemo(() => {
|
const handleIndicatorUpClick = useCallback(() => {
|
||||||
return panelInfo?.showUrl;
|
// MediaPanel에서는 indicator 사용 안 함
|
||||||
}, [panelInfo?.showUrl]);
|
}, []);
|
||||||
|
|
||||||
const isYoutube = useMemo(() => {
|
// ✅ modal 스타일 설정
|
||||||
if (currentPlayingUrl && currentPlayingUrl.includes('youtu')) {
|
useEffect(() => {
|
||||||
return true;
|
let resizeObserver = null;
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}, [currentPlayingUrl]);
|
|
||||||
|
|
||||||
const currentSubtitleUrl = useMemo(() => {
|
if (panelInfo.modal && panelInfo.modalContainerId) {
|
||||||
return panelInfo?.subtitle;
|
// modal 모드: modalContainerId 기반으로 위치와 크기 계산
|
||||||
}, [panelInfo?.subtitle]);
|
const node = document.querySelector(`[data-spotlight-id="${panelInfo.modalContainerId}"]`);
|
||||||
|
if (node) {
|
||||||
|
const { width, height, top, left } = node.getBoundingClientRect();
|
||||||
|
|
||||||
const reactPlayerSubtitleConfig = useMemo(() => {
|
// ProductVideo의 padding(6px * 2)과 추가 여유를 고려하여 크기 조정
|
||||||
if (isSubtitleActive && currentSubtitleUrl) {
|
// 비디오가 오른쪽으로 넘치지 않도록 충분한 여유 확보
|
||||||
return {
|
const paddingOffset = 6 * 2; // padding 양쪽
|
||||||
file: {
|
const extraMargin = 6 * 2; // 추가 여유 (포커스 테두리 + 비디오 비율 고려)
|
||||||
attributes: {
|
const totalOffset = paddingOffset + extraMargin; // 24px
|
||||||
crossOrigin: 'true',
|
|
||||||
},
|
|
||||||
tracks: [{ kind: 'subtitles', src: currentSubtitleUrl, default: true }],
|
|
||||||
},
|
|
||||||
youtube: YOUTUBECONFIG,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
youtube: YOUTUBECONFIG,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}, [currentSubtitleUrl, isSubtitleActive]);
|
|
||||||
|
|
||||||
const videoType = useMemo(() => {
|
const adjustedWidth = width - totalOffset;
|
||||||
if (currentPlayingUrl) {
|
const adjustedHeight = height - totalOffset;
|
||||||
if (currentPlayingUrl.toLowerCase().endsWith('.mp4')) {
|
const adjustedTop = top + totalOffset / 2;
|
||||||
return 'video/mp4';
|
const adjustedLeft = left + totalOffset / 2;
|
||||||
} else if (currentPlayingUrl.toLowerCase().endsWith('.mpd')) {
|
|
||||||
return 'application/dash+xml';
|
|
||||||
} else if (currentPlayingUrl.toLowerCase().endsWith('.m3u8')) {
|
|
||||||
return 'application/mpegurl';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 'application/mpegurl';
|
|
||||||
}, [currentPlayingUrl]);
|
|
||||||
|
|
||||||
const videoThumbnailUrl = useMemo(() => {
|
const style = {
|
||||||
return panelInfo?.thumbnailUrl;
|
width: adjustedWidth + 'px',
|
||||||
}, [panelInfo?.thumbnailUrl]);
|
height: adjustedHeight + 'px',
|
||||||
|
maxWidth: adjustedWidth + 'px',
|
||||||
const mediainfoHandler = useCallback(
|
maxHeight: adjustedHeight + 'px',
|
||||||
(ev) => {
|
top: adjustedTop + 'px',
|
||||||
const type = ev.type;
|
left: adjustedLeft + 'px',
|
||||||
if (type !== 'timeupdate' && type !== 'durationchange') {
|
position: 'fixed',
|
||||||
debugLog('mediainfoHandler....', type, ev, videoPlayer.current?.getMediaState());
|
overflow: 'hidden', // visible → hidden으로 변경하여 넘치는 부분 숨김
|
||||||
}
|
};
|
||||||
|
setModalStyle(style);
|
||||||
// ✅ hlsError 처리 강화
|
let scale = 1;
|
||||||
if (ev === 'hlsError') {
|
if (typeof window === 'object') {
|
||||||
|
scale = adjustedWidth / window.innerWidth;
|
||||||
|
setModalScale(scale);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setModalStyle(panelInfo.modalStyle || {});
|
||||||
|
setModalScale(panelInfo.modalScale || 1);
|
||||||
|
}
|
||||||
|
} else if (isOnTop && !panelInfo.modal && !panelInfo.isMinimized && videoPlayer.current) {
|
||||||
|
// ✅ 안전한 메서드 호출로 null/undefined 체크
|
||||||
const mediaState = safePlayerCall('getMediaState');
|
const mediaState = safePlayerCall('getMediaState');
|
||||||
if (mediaState && isNaN(Number(mediaState.playbackRate))) {
|
if (mediaState?.paused) {
|
||||||
dispatch(
|
safePlayerCall('play');
|
||||||
sendBroadCast({
|
}
|
||||||
type: 'videoError',
|
|
||||||
moreInfo: { reason: 'hlsError' },
|
const isControlsHidden =
|
||||||
})
|
videoPlayer.current.areControlsVisible && !videoPlayer.current.areControlsVisible();
|
||||||
);
|
if (isControlsHidden) {
|
||||||
|
safePlayerCall('showControls');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ cleanup: resize observer 정리
|
||||||
|
return () => {
|
||||||
|
if (resizeObserver) {
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [panelInfo, isOnTop]);
|
||||||
|
|
||||||
|
// ✅ 비디오 클릭 시 modal → fullscreen 전환 또는 controls 토글
|
||||||
|
const onVideoClick = useCallback(() => {
|
||||||
|
if (panelInfo.modal) {
|
||||||
|
// debugLog('[MediaPanel] Video clicked - switching to fullscreen');
|
||||||
|
dispatch(switchMediaToFullscreen());
|
||||||
|
} else {
|
||||||
|
// 비디오 클릭 시 controls 토글
|
||||||
|
safePlayerCall('toggleControls');
|
||||||
|
}
|
||||||
|
}, [dispatch, panelInfo.modal, safePlayerCall]);
|
||||||
|
|
||||||
|
const onClickBack = useCallback(
|
||||||
|
(ev) => {
|
||||||
|
// ✅ 뒤로가기 시 비디오 정지
|
||||||
|
safePlayerCall('pause');
|
||||||
|
|
||||||
|
// modal에서 full로 전환된 경우 다시 modal로 돌아감
|
||||||
|
if (panelInfo.modalContainerId && !panelInfo.modal) {
|
||||||
|
// 다시 modal로 돌리는 로직은 startVideoPlayer 액션을 사용할 수도 있지만
|
||||||
|
// 여기서는 단순히 패널을 pop
|
||||||
|
dispatch(PanelActions.popPanel());
|
||||||
|
ev?.stopPropagation();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
switch (type) {
|
if (!panelInfo.modal) {
|
||||||
case 'timeupdate': {
|
dispatch(PanelActions.popPanel());
|
||||||
|
ev?.stopPropagation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[dispatch, panelInfo, safePlayerCall]
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentPlayingUrl = useMemo(() => {
|
||||||
|
return panelInfo?.showUrl;
|
||||||
|
}, [panelInfo?.showUrl]);
|
||||||
|
|
||||||
|
const isYoutube = useMemo(() => {
|
||||||
|
if (currentPlayingUrl && currentPlayingUrl.includes('youtu')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}, [currentPlayingUrl]);
|
||||||
|
|
||||||
|
const currentSubtitleUrl = useMemo(() => {
|
||||||
|
return panelInfo?.subtitle;
|
||||||
|
}, [panelInfo?.subtitle]);
|
||||||
|
|
||||||
|
const reactPlayerSubtitleConfig = useMemo(() => {
|
||||||
|
if (isSubtitleActive && currentSubtitleUrl) {
|
||||||
|
return {
|
||||||
|
file: {
|
||||||
|
attributes: {
|
||||||
|
crossOrigin: 'true',
|
||||||
|
},
|
||||||
|
tracks: [{ kind: 'subtitles', src: currentSubtitleUrl, default: true }],
|
||||||
|
},
|
||||||
|
youtube: YOUTUBECONFIG,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
youtube: YOUTUBECONFIG,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [currentSubtitleUrl, isSubtitleActive]);
|
||||||
|
|
||||||
|
const videoType = useMemo(() => {
|
||||||
|
if (currentPlayingUrl) {
|
||||||
|
if (currentPlayingUrl.toLowerCase().endsWith('.mp4')) {
|
||||||
|
return 'video/mp4';
|
||||||
|
} else if (currentPlayingUrl.toLowerCase().endsWith('.mpd')) {
|
||||||
|
return 'application/dash+xml';
|
||||||
|
} else if (currentPlayingUrl.toLowerCase().endsWith('.m3u8')) {
|
||||||
|
return 'application/mpegurl';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 'application/mpegurl';
|
||||||
|
}, [currentPlayingUrl]);
|
||||||
|
|
||||||
|
const videoThumbnailUrl = useMemo(() => {
|
||||||
|
return panelInfo?.thumbnailUrl;
|
||||||
|
}, [panelInfo?.thumbnailUrl]);
|
||||||
|
|
||||||
|
const mediainfoHandler = useCallback(
|
||||||
|
(ev) => {
|
||||||
|
const type = ev.type;
|
||||||
|
if (type !== 'timeupdate' && type !== 'durationchange') {
|
||||||
|
debugLog('mediainfoHandler....', type, ev, videoPlayer.current?.getMediaState());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ hlsError 처리 강화
|
||||||
|
if (ev === 'hlsError') {
|
||||||
const mediaState = safePlayerCall('getMediaState');
|
const mediaState = safePlayerCall('getMediaState');
|
||||||
if (mediaState) {
|
if (mediaState && isNaN(Number(mediaState.playbackRate))) {
|
||||||
setCurrentTime(mediaState.currentTime || 0);
|
dispatch(
|
||||||
|
sendBroadCast({
|
||||||
|
type: 'videoError',
|
||||||
|
moreInfo: { reason: 'hlsError' },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case 'error': {
|
|
||||||
const mediaState = safePlayerCall('getMediaState');
|
switch (type) {
|
||||||
const errorInfo = mediaState?.error || 'unknown';
|
case 'timeupdate': {
|
||||||
dispatch(
|
const mediaState = safePlayerCall('getMediaState');
|
||||||
sendBroadCast({
|
if (mediaState) {
|
||||||
type: 'videoError',
|
setCurrentTime(mediaState.currentTime || 0);
|
||||||
moreInfo: { reason: errorInfo },
|
}
|
||||||
})
|
break;
|
||||||
);
|
}
|
||||||
break;
|
case 'error': {
|
||||||
|
const mediaState = safePlayerCall('getMediaState');
|
||||||
|
const errorInfo = mediaState?.error || 'unknown';
|
||||||
|
dispatch(
|
||||||
|
sendBroadCast({
|
||||||
|
type: 'videoError',
|
||||||
|
moreInfo: { reason: errorInfo },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'loadeddata': {
|
||||||
|
setVideoLoaded(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
case 'loadeddata': {
|
},
|
||||||
setVideoLoaded(true);
|
[dispatch, safePlayerCall]
|
||||||
break;
|
);
|
||||||
|
|
||||||
|
const onEnded = useCallback(
|
||||||
|
(e) => {
|
||||||
|
debugLog('[MediaPanel] Video ended');
|
||||||
|
// continuousPlay는 MediaPlayer(VideoPlayer) 컴포넌트 내부에서 loop 속성으로 처리
|
||||||
|
// onEnded가 호출되면 loop=false 인 경우이므로 패널을 닫음
|
||||||
|
Spotlight.pause();
|
||||||
|
|
||||||
|
// ✅ 이전 타이머가 있으면 정리
|
||||||
|
if (onEndedTimerRef.current) {
|
||||||
|
clearTimeout(onEndedTimerRef.current);
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[dispatch, safePlayerCall]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onEnded = useCallback(
|
// ✅ 새로운 타이머 저장 (cleanup 시 정리용)
|
||||||
(e) => {
|
onEndedTimerRef.current = setTimeout(() => {
|
||||||
debugLog('[MediaPanel] Video ended');
|
Spotlight.resume();
|
||||||
// continuousPlay는 MediaPlayer(VideoPlayer) 컴포넌트 내부에서 loop 속성으로 처리
|
dispatch(PanelActions.popPanel(panel_names.MEDIA_PANEL));
|
||||||
// onEnded가 호출되면 loop=false 인 경우이므로 패널을 닫음
|
onEndedTimerRef.current = null;
|
||||||
Spotlight.pause();
|
}, 1500);
|
||||||
|
|
||||||
// ✅ 이전 타이머가 있으면 정리
|
e?.stopPropagation();
|
||||||
if (onEndedTimerRef.current) {
|
e?.preventDefault();
|
||||||
clearTimeout(onEndedTimerRef.current);
|
},
|
||||||
}
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
// ✅ 새로운 타이머 저장 (cleanup 시 정리용)
|
// ✅ useLayoutEffect: DOM 스타일 설정 (메모리 누수 방지)
|
||||||
onEndedTimerRef.current = setTimeout(() => {
|
useLayoutEffect(() => {
|
||||||
Spotlight.resume();
|
const videoContainer = document.querySelector(`.${css.videoContainer}`);
|
||||||
dispatch(PanelActions.popPanel(panel_names.MEDIA_PANEL));
|
if (videoContainer && panelInfo.thumbnailUrl && !videoLoaded) {
|
||||||
onEndedTimerRef.current = null;
|
|
||||||
}, 1500);
|
|
||||||
|
|
||||||
e?.stopPropagation();
|
|
||||||
e?.preventDefault();
|
|
||||||
},
|
|
||||||
[dispatch]
|
|
||||||
);
|
|
||||||
|
|
||||||
// ✅ useLayoutEffect: DOM 스타일 설정 (메모리 누수 방지)
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
const videoContainer = document.querySelector(`.${css.videoContainer}`);
|
|
||||||
if (videoContainer && panelInfo.thumbnailUrl && !videoLoaded) {
|
|
||||||
try {
|
|
||||||
videoContainer.style.background = `url(${panelInfo.thumbnailUrl}) center center / contain no-repeat`;
|
|
||||||
videoContainer.style.backgroundColor = 'black';
|
|
||||||
} catch (err) {
|
|
||||||
if (DEBUG_MODE) console.warn('[MediaPanel] 썸네일 스타일 설정 실패:', err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ cleanup: 컴포넌트 언마운트 시 DOM 스타일 초기화
|
|
||||||
return () => {
|
|
||||||
if (videoContainer) {
|
|
||||||
try {
|
try {
|
||||||
videoContainer.style.background = '';
|
videoContainer.style.background = `url(${panelInfo.thumbnailUrl}) center center / contain no-repeat`;
|
||||||
videoContainer.style.backgroundColor = '';
|
videoContainer.style.backgroundColor = 'black';
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// 스타일 초기화 실패는 무시
|
if (DEBUG_MODE) console.warn('[MediaPanel] 썸네일 스타일 설정 실패:', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}, [panelInfo.thumbnailUrl, videoLoaded]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
// ✅ cleanup: 컴포넌트 언마운트 시 DOM 스타일 초기화
|
||||||
setVideoLoaded(false);
|
return () => {
|
||||||
}, [currentPlayingUrl]);
|
if (videoContainer) {
|
||||||
|
|
||||||
// ✅ 컴포넌트 언마운트 시 모든 타이머 및 리소스 정리
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
// ✅ onEnded 타이머 정리
|
|
||||||
if (onEndedTimerRef.current) {
|
|
||||||
clearTimeout(onEndedTimerRef.current);
|
|
||||||
onEndedTimerRef.current = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ 비디오 플레이어 정지 및 정리
|
|
||||||
if (videoPlayer.current) {
|
|
||||||
try {
|
|
||||||
// 재생 중이면 정지
|
|
||||||
safePlayerCall('pause');
|
|
||||||
|
|
||||||
// controls 타임아웃 정리
|
|
||||||
safePlayerCall('hideControls');
|
|
||||||
} catch (err) {
|
|
||||||
if (DEBUG_MODE) console.warn('[MediaPanel] 비디오 정지 실패:', err);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ref 초기화
|
|
||||||
videoPlayer.current = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ 이벤트 리스너 정리
|
|
||||||
if (mediaEventListenersRef.current && mediaEventListenersRef.current.length > 0) {
|
|
||||||
mediaEventListenersRef.current.forEach(({ element, event, handler }) => {
|
|
||||||
try {
|
try {
|
||||||
element?.removeEventListener?.(event, handler);
|
videoContainer.style.background = '';
|
||||||
|
videoContainer.style.backgroundColor = '';
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// 리스너 제거 실패는 무시
|
// 스타일 초기화 실패는 무시
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
mediaEventListenersRef.current = [];
|
};
|
||||||
}
|
}, [panelInfo.thumbnailUrl, videoLoaded]);
|
||||||
|
|
||||||
// ✅ Spotlight 상태 초기화
|
useEffect(() => {
|
||||||
try {
|
setVideoLoaded(false);
|
||||||
Spotlight.resume?.();
|
}, [currentPlayingUrl]);
|
||||||
} catch (err) {
|
|
||||||
// Spotlight 초기화 실패는 무시
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [dispatch, safePlayerCall]);
|
|
||||||
|
|
||||||
// debugLog('[MediaPanel] ========== Rendering ==========');
|
// ✅ 컴포넌트 언마운트 시 모든 타이머 및 리소스 정리
|
||||||
// debugLog('[MediaPanel] isOnTop:', isOnTop);
|
useEffect(() => {
|
||||||
// debugLog('[MediaPanel] panelInfo.modal:', panelInfo.modal);
|
return () => {
|
||||||
// debugLog('[MediaPanel] panelInfo.isMinimized:', panelInfo.isMinimized);
|
// ✅ onEnded 타이머 정리
|
||||||
// debugLog('[MediaPanel] panelInfo.isPaused:', panelInfo.isPaused);
|
if (onEndedTimerRef.current) {
|
||||||
// debugLog('[MediaPanel] currentPlayingUrl:', currentPlayingUrl);
|
clearTimeout(onEndedTimerRef.current);
|
||||||
// debugLog('[MediaPanel] hasVideoPlayer:', !!videoPlayer.current);
|
onEndedTimerRef.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
// classNames 적용 상태 확인
|
// ✅ 비디오 플레이어 정지 및 정리
|
||||||
// debugLog('[MediaPanel] ========== ClassNames Analysis ==========');
|
if (videoPlayer.current) {
|
||||||
// debugLog('[MediaPanel] css.videoContainer:', css.videoContainer);
|
try {
|
||||||
// debugLog('[MediaPanel] Condition [panelInfo.modal && !panelInfo.isMinimized]:', panelInfo.modal && !panelInfo.isMinimized);
|
// 재생 중이면 정지
|
||||||
// debugLog('[MediaPanel] css.modal:', css.modal);
|
safePlayerCall('pause');
|
||||||
// debugLog('[MediaPanel] Condition [panelInfo.isMinimized]:', panelInfo.isMinimized);
|
|
||||||
// debugLog('[MediaPanel] css["modal-minimized"]:', css['modal-minimized']);
|
|
||||||
// debugLog('[MediaPanel] Condition [!isOnTop]:', !isOnTop);
|
|
||||||
// debugLog('[MediaPanel] css.background:', css.background);
|
|
||||||
|
|
||||||
const appliedClassNames = classNames(
|
// controls 타임아웃 정리
|
||||||
css.videoContainer,
|
safePlayerCall('hideControls');
|
||||||
panelInfo.modal && !panelInfo.isMinimized && css.modal,
|
} catch (err) {
|
||||||
panelInfo.isMinimized && css['modal-minimized'],
|
if (DEBUG_MODE) console.warn('[MediaPanel] 비디오 정지 실패:', err);
|
||||||
!isOnTop && css.background
|
}
|
||||||
);
|
|
||||||
// debugLog('[MediaPanel] Final Applied ClassNames:', appliedClassNames);
|
|
||||||
// debugLog('[MediaPanel] modalStyle:', modalStyle);
|
|
||||||
// debugLog('[MediaPanel] modalScale:', modalScale);
|
|
||||||
// debugLog('[MediaPanel] ===============================================');
|
|
||||||
|
|
||||||
// minimized 상태일 때는 spotlightRestrict 해제 (포커스 이동 허용)
|
// ref 초기화
|
||||||
const containerSpotlightRestrict = panelInfo.isMinimized ? 'none' : 'self-only';
|
videoPlayer.current = null;
|
||||||
const shouldDisableIframeInteraction = isYoutube && !panelInfo.modal;
|
}
|
||||||
|
|
||||||
return (
|
// ✅ 이벤트 리스너 정리
|
||||||
<TPanel
|
if (mediaEventListenersRef.current && mediaEventListenersRef.current.length > 0) {
|
||||||
isTabActivated={false}
|
mediaEventListenersRef.current.forEach(({ element, event, handler }) => {
|
||||||
{...props}
|
try {
|
||||||
className={appliedClassNames}
|
element?.removeEventListener?.(event, handler);
|
||||||
handleCancel={onClickBack}
|
} catch (err) {
|
||||||
spotlightId={spotlightId}
|
// 리스너 제거 실패는 무시
|
||||||
>
|
}
|
||||||
<Container
|
});
|
||||||
spotlightRestrict={containerSpotlightRestrict}
|
mediaEventListenersRef.current = [];
|
||||||
spotlightId="spotlightId-media-video-container"
|
}
|
||||||
|
|
||||||
|
// ✅ Spotlight 상태 초기화
|
||||||
|
try {
|
||||||
|
Spotlight.resume?.();
|
||||||
|
} catch (err) {
|
||||||
|
// Spotlight 초기화 실패는 무시
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [dispatch, safePlayerCall]);
|
||||||
|
|
||||||
|
// debugLog('[MediaPanel] ========== Rendering ==========');
|
||||||
|
// debugLog('[MediaPanel] isOnTop:', isOnTop);
|
||||||
|
// debugLog('[MediaPanel] panelInfo.modal:', panelInfo.modal);
|
||||||
|
// debugLog('[MediaPanel] panelInfo.isMinimized:', panelInfo.isMinimized);
|
||||||
|
// debugLog('[MediaPanel] panelInfo.isPaused:', panelInfo.isPaused);
|
||||||
|
// debugLog('[MediaPanel] currentPlayingUrl:', currentPlayingUrl);
|
||||||
|
// debugLog('[MediaPanel] hasVideoPlayer:', !!videoPlayer.current);
|
||||||
|
|
||||||
|
// classNames 적용 상태 확인
|
||||||
|
// debugLog('[MediaPanel] ========== ClassNames Analysis ==========');
|
||||||
|
// debugLog('[MediaPanel] css.videoContainer:', css.videoContainer);
|
||||||
|
// debugLog('[MediaPanel] Condition [panelInfo.modal && !panelInfo.isMinimized]:', panelInfo.modal && !panelInfo.isMinimized);
|
||||||
|
// debugLog('[MediaPanel] css.modal:', css.modal);
|
||||||
|
// debugLog('[MediaPanel] Condition [panelInfo.isMinimized]:', panelInfo.isMinimized);
|
||||||
|
// debugLog('[MediaPanel] css["modal-minimized"]:', css['modal-minimized']);
|
||||||
|
// debugLog('[MediaPanel] Condition [!isOnTop]:', !isOnTop);
|
||||||
|
// debugLog('[MediaPanel] css.background:', css.background);
|
||||||
|
|
||||||
|
const appliedClassNames = classNames(
|
||||||
|
css.videoContainer,
|
||||||
|
panelInfo.modal && !panelInfo.isMinimized && css.modal,
|
||||||
|
panelInfo.isMinimized && css['modal-minimized'],
|
||||||
|
!isOnTop && css.background
|
||||||
|
);
|
||||||
|
// debugLog('[MediaPanel] Final Applied ClassNames:', appliedClassNames);
|
||||||
|
// debugLog('[MediaPanel] modalStyle:', modalStyle);
|
||||||
|
// debugLog('[MediaPanel] modalScale:', modalScale);
|
||||||
|
// debugLog('[MediaPanel] ===============================================');
|
||||||
|
|
||||||
|
// minimized 상태일 때는 spotlightRestrict 해제 (포커스 이동 허용)
|
||||||
|
const containerSpotlightRestrict = panelInfo.isMinimized ? 'none' : 'self-only';
|
||||||
|
const shouldDisableIframeInteraction = isYoutube && !panelInfo.modal;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TPanel
|
||||||
|
isTabActivated={false}
|
||||||
|
{...props}
|
||||||
|
className={appliedClassNames}
|
||||||
|
handleCancel={onClickBack}
|
||||||
|
spotlightId={spotlightId}
|
||||||
>
|
>
|
||||||
{currentPlayingUrl && (
|
<Container
|
||||||
<div
|
spotlightRestrict={containerSpotlightRestrict}
|
||||||
className={classNames(
|
spotlightId="spotlightId-media-video-container"
|
||||||
css.videoPlayerWrapper,
|
>
|
||||||
shouldDisableIframeInteraction && css.youtubeSafe
|
{currentPlayingUrl && (
|
||||||
)}
|
<div
|
||||||
>
|
className={classNames(
|
||||||
<MediaPlayerV2
|
css.videoPlayerWrapper,
|
||||||
setApiProvider={getPlayer}
|
shouldDisableIframeInteraction && css.youtubeSafe
|
||||||
disabled={panelInfo.modal}
|
|
||||||
onEnded={onEnded}
|
|
||||||
autoPlay={!panelInfo?.isPaused}
|
|
||||||
noAutoShowMediaControls={panelInfo.modal} // modal 모드에서는 자동으로 controls가 올라오지 않도록 설정
|
|
||||||
autoCloseTimeout={3000}
|
|
||||||
onBackButton={onClickBack}
|
|
||||||
onClick={onVideoClick}
|
|
||||||
spotlightDisabled={panelInfo.modal}
|
|
||||||
isYoutube={isYoutube}
|
|
||||||
src={currentPlayingUrl}
|
|
||||||
loop={panelInfo.continuousPlay || false}
|
|
||||||
style={panelInfo.modal ? modalStyle : {}}
|
|
||||||
modalScale={panelInfo.modal ? modalScale : 1}
|
|
||||||
modalClassName={panelInfo.modal ? panelInfo.modalClassName : undefined}
|
|
||||||
onError={mediainfoHandler}
|
|
||||||
onTimeUpdate={mediainfoHandler}
|
|
||||||
onLoadedData={mediainfoHandler}
|
|
||||||
onLoadedMetadata={mediainfoHandler}
|
|
||||||
onDurationChange={mediainfoHandler}
|
|
||||||
reactPlayerConfig={reactPlayerSubtitleConfig}
|
|
||||||
thumbnailUrl={videoLoaded ? '' : videoThumbnailUrl}
|
|
||||||
videoComponent={
|
|
||||||
(typeof window === 'object' && !window.PalmSystem) || isYoutube ? TReactPlayer : Media
|
|
||||||
}
|
|
||||||
// VideoOverlay props - 간소화
|
|
||||||
type="MEDIA"
|
|
||||||
panelInfo={panelInfo}
|
|
||||||
captionEnable={false}
|
|
||||||
setIsSubtitleActive={setIsSubtitleActive}
|
|
||||||
setCurrentTime={setCurrentTime}
|
|
||||||
setIsVODPaused={setIsVODPaused}
|
|
||||||
// PlayerOverlayContents props (빈 배열로 전달하여 null 처리)
|
|
||||||
playListInfo={[]}
|
|
||||||
selectedIndex={0}
|
|
||||||
videoVerticalVisible={false}
|
|
||||||
sideContentsVisible={false}
|
|
||||||
setSideContentsVisible={setSideContentsVisible}
|
|
||||||
handleIndicatorDownClick={handleIndicatorDownClick}
|
|
||||||
handleIndicatorUpClick={handleIndicatorUpClick}
|
|
||||||
>
|
|
||||||
{typeof window === 'object' && window.PalmSystem && (
|
|
||||||
<source src={currentPlayingUrl} type={videoType} />
|
|
||||||
)}
|
)}
|
||||||
{isSubtitleActive &&
|
>
|
||||||
!panelInfo.modal &&
|
<MediaPlayerV2
|
||||||
typeof window === 'object' &&
|
setApiProvider={getPlayer}
|
||||||
window.PalmSystem &&
|
disabled={panelInfo.modal}
|
||||||
currentSubtitleUrl && <track kind="subtitles" src={currentSubtitleUrl} default />}
|
onEnded={onEnded}
|
||||||
</MediaPlayerV2>
|
autoPlay={!panelInfo?.isPaused}
|
||||||
</div>
|
noAutoShowMediaControls={panelInfo.modal} // modal 모드에서는 자동으로 controls가 올라오지 않도록 설정
|
||||||
)}
|
autoCloseTimeout={3000}
|
||||||
</Container>
|
onBackButton={onClickBack}
|
||||||
</TPanel>
|
onClick={onVideoClick}
|
||||||
);
|
spotlightDisabled={panelInfo.modal}
|
||||||
};
|
isYoutube={isYoutube}
|
||||||
|
src={currentPlayingUrl}
|
||||||
|
loop={panelInfo.continuousPlay || false}
|
||||||
|
style={panelInfo.modal ? modalStyle : {}}
|
||||||
|
modalScale={panelInfo.modal ? modalScale : 1}
|
||||||
|
modalClassName={panelInfo.modal ? panelInfo.modalClassName : undefined}
|
||||||
|
onError={mediainfoHandler}
|
||||||
|
onTimeUpdate={mediainfoHandler}
|
||||||
|
onLoadedData={mediainfoHandler}
|
||||||
|
onLoadedMetadata={mediainfoHandler}
|
||||||
|
onDurationChange={mediainfoHandler}
|
||||||
|
reactPlayerConfig={reactPlayerSubtitleConfig}
|
||||||
|
thumbnailUrl={videoLoaded ? '' : videoThumbnailUrl}
|
||||||
|
videoComponent={
|
||||||
|
(typeof window === 'object' && !window.PalmSystem) || isYoutube
|
||||||
|
? TReactPlayer
|
||||||
|
: Media
|
||||||
|
}
|
||||||
|
// VideoOverlay props - 간소화
|
||||||
|
type="MEDIA"
|
||||||
|
panelInfo={panelInfo}
|
||||||
|
captionEnable={false}
|
||||||
|
setIsSubtitleActive={setIsSubtitleActive}
|
||||||
|
setCurrentTime={setCurrentTime}
|
||||||
|
setIsVODPaused={setIsVODPaused}
|
||||||
|
// PlayerOverlayContents props (빈 배열로 전달하여 null 처리)
|
||||||
|
playListInfo={[]}
|
||||||
|
selectedIndex={0}
|
||||||
|
videoVerticalVisible={false}
|
||||||
|
sideContentsVisible={false}
|
||||||
|
setSideContentsVisible={setSideContentsVisible}
|
||||||
|
handleIndicatorDownClick={handleIndicatorDownClick}
|
||||||
|
handleIndicatorUpClick={handleIndicatorUpClick}
|
||||||
|
>
|
||||||
|
{typeof window === 'object' && window.PalmSystem && (
|
||||||
|
<source src={currentPlayingUrl} type={videoType} />
|
||||||
|
)}
|
||||||
|
{isSubtitleActive &&
|
||||||
|
!panelInfo.modal &&
|
||||||
|
typeof window === 'object' &&
|
||||||
|
window.PalmSystem &&
|
||||||
|
currentSubtitleUrl && <track kind="subtitles" src={currentSubtitleUrl} default />}
|
||||||
|
</MediaPlayerV2>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
|
</TPanel>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export default React.memo(MediaPanel);
|
export default React.memo(MediaPanel);
|
||||||
|
|||||||
Reference in New Issue
Block a user