video player panel overlay

This commit is contained in:
yonghyon
2024-04-23 02:42:14 +09:00
parent a336da2c94
commit 28abf31d6c
19 changed files with 459 additions and 280 deletions

View File

@@ -1,8 +1,16 @@
import { types } from "./actionTypes";
export const pushPanel = (panel) => ({
/*
name: panel_names.PLAYER_PANEL,
panelInfo: {
modal: true //only for video player
etc...
},
*/
export const pushPanel = (panel, duplicatable=false) => ({
type: types.PUSH_PANEL,
payload: panel,
duplicatable: duplicatable
});
export const popPanel = (panelName) => ({

View File

@@ -1,7 +1,55 @@
import Spotlight from '@enact/spotlight';
import { URLS } from '../api/apiConfig';
import { TAxios } from '../api/TAxios';
import { panel_names } from '../utils/Config';
import { types } from './actionTypes';
import { popPanel, pushPanel, updatePanel } from './panelActions';
//yhcho
/*
dispatch(startVideoPreview({
patnrId: randomData.patnrId,
showId: randomData.showId,
shptmBanrTpNm: randomData.shptmBanrTpNm,
lgCatCd: randomData.lgCatCd,
modal: true,
modalContainerRef: videoModalContainerRef.current, //to calc width, height, left, top
modalClassName:css.videoModal
}));
modalClassName: modal more class info and checking it's launched from preview mode
*/
let startVideoTimer = null;
//start modal mode
//start Full -> modal mode
export const startVideoPlayer = ({ modal, modalContainerId, modalClassName, ...rest }) => (dispatch, getState) => {
const panels = getState().panels.panels;
const topPanel = panels[panels.length -1];
let panelWorkingAction = pushPanel;
if(topPanel && topPanel.name === panel_names.PLAYER_PANEL){
panelWorkingAction = updatePanel;
}
dispatch(panelWorkingAction({
name: panel_names.PLAYER_PANEL,
panelInfo: {
modal,
modalContainerId,
modalClassName,
...rest
}
}, true));
if(modal && modalContainerId){
Spotlight.focus(modalContainerId);
}
};
export const finishVideoPreview = () => (dispatch, getState) => {
const panels = getState().panels.panels;
const topPanel = panels[panels.length -1];
if(topPanel && topPanel.name === panel_names.PLAYER_PANEL && topPanel.panelInfo.modal){
dispatch(popPanel());
}
};
// 채팅 로그 가져오기 IF-LGSP-371
export const getChatLog =
({ patnrId, showId }) =>

View File

@@ -5,7 +5,7 @@
.size(@w: 100%, @h: 100vh);
box-sizing: border-box;
color: black;
position: absolute; //yhcho
> section {
font-family: @baseFont;
color: @COLOR_GRAY03;

View File

@@ -5,7 +5,7 @@ import React, { useCallback, useRef, useMemo } from "react";
var handledMediaEventsMap =
['onReady', 'onStart', 'onPlay', 'onProgress', 'onDuration', 'onPause', 'onBuffer', 'onBufferEnd', 'onSeek', 'onEnded', 'onError'];
export default function TReactPlayer({mediaEventsMap=handledMediaEventsMap, videoRef, ...rest}) {
export default function TReactPlayer({mediaEventsMap=handledMediaEventsMap, videoRef, url, ...rest}) {
const playerRef = useRef(null);
const handleEvent = useCallback((type)=> (ev)=> {
@@ -55,6 +55,7 @@ export default function TReactPlayer({mediaEventsMap=handledMediaEventsMap, vide
return (
<ReactPlayer
ref={playerRef}
url={url}
progressInterval={1000}
{...handledMediaEvents}
{...rest}

View File

@@ -732,6 +732,7 @@ const VideoPlayerBase = class extends React.Component {
introTime: PropTypes.number,
onClickSkipIntro: PropTypes.func,
onIntroDisabled: PropTypes.func,
modalClassName: PropTypes.any,
src: PropTypes.string, //for ReactPlayer
};
@@ -1388,6 +1389,7 @@ const VideoPlayerBase = class extends React.Component {
handleEvent = () => {
const el = this.video;
if(!el){
console.log('yhcho VideoPlayer no el ');
return;
}
const updatedState = {
@@ -1446,7 +1448,7 @@ const VideoPlayerBase = class extends React.Component {
currentTime: this.state.currentTime,
duration: this.state.duration,
paused: this.state.paused,
playbackRate: this.video.playbackRate,
playbackRate: this.video?.playbackRate,
proportionLoaded: this.state.proportionLoaded,
proportionPlayed: this.state.proportionPlayed,
};
@@ -2040,6 +2042,7 @@ const VideoPlayerBase = class extends React.Component {
render() {
const {
className,
modalClassName,
disabled,
infoComponents,
backButton,
@@ -2141,7 +2144,7 @@ const VideoPlayerBase = class extends React.Component {
return (
<RootContainer
className={
css.videoPlayer + " enact-fit" + (className ? " " + className : "")
classNames(css.videoPlayer + " enact-fit" + (className ? " " + className : ""), modalClassName)
}
onClick={this.activityDetected}
ref={this.setPlayerRef}

View File

@@ -60,7 +60,9 @@
background-size: cover;
border-radius: 20.8125rem;
overflow: hidden;
margin: 490px auto;
// margin: 490px auto;
left: calc(50% - 50px);
top: calc(50% - 50px);
animation: none 1.25s linear infinite;
animation-name: spin;
// animation-play-state: paused;

View File

@@ -25,7 +25,7 @@ export const panelsReducer = (state = initialState, action) => {
if (forceTopIndex >= 0) {
forceTopPanelsInfo[forceTopIndex] = panel;
} else if (panel.name !== action.payload.name) {
} else if (panel.name !== action.payload.name || action.duplicatable) {
newState.push(panel);
}
});
@@ -67,10 +67,19 @@ export const panelsReducer = (state = initialState, action) => {
}
case types.UPDATE_PANEL:
{
let lastIndex = -1;
// 배열의 끝에서부터 시작하여 조건에 맞는 마지막 인덱스 찾기
for (let i = state.panels.length - 1; i >= 0; i--) {
if (state.panels[i].name === action.payload.name) {
lastIndex = i;
break; // 조건에 맞는 첫 번째 요소를 찾으면 루프 종료
}
}
return {
...state,
panels: state.panels.map((panel) =>
panel.name === action.payload.name
panels: state.panels.map((panel, index) =>
index === lastIndex
? {
...panel,
panelInfo: { ...panel.panelInfo, ...action.payload.panelInfo },
@@ -78,7 +87,7 @@ export const panelsReducer = (state = initialState, action) => {
: panel
),
};
}
case types.UPDATE_MODAL_STATUS:
return {
...state,

View File

@@ -13,14 +13,16 @@ import { clearProductDetail } from "../../actions/productActions";
import TBody from "../../components/TBody/TBody";
import THeader from "../../components/THeader/THeader";
import TPanel from "../../components/TPanel/TPanel";
import { panel_names } from "../../utils/Config";
import css from "./DetailPanel.module.less";
import GroupProduct from "./GroupProduct/GroupProduct";
import SingleProduct from "./SingleProduct/SingleProduct";
import ThemeProduct from "./ThemeProduct/ThemeProduct";
import UnableProduct from "./UnableProduct/UnableProduct";
import YouMayLike from "./YouMayLike/YouMayLike";
import { finishVideoPreview } from "../../actions/playActions";
export default function DetailPanel() {
export default function ItemDetail({panelInfo}) {
const [selectedPatnrId, setSelectedPatnrId] = useState("");
const [selectedPrdtId, setSelectedPrtdId] = useState("");
const [selectedCurationId, setSelectedCurationId] = useState("");
@@ -94,6 +96,8 @@ export default function DetailPanel() {
}, [dispatch, selectedPatnrId, selectedPrdtId, panels]);
const onClick = useCallback(() => {
// dispatch(resetPanels([{ name: panel_names.DETAIL_PANEL, panelInfo: {} }]));
dispatch(finishVideoPreview());
dispatch(popPanel());
}, [dispatch]);
@@ -184,6 +188,7 @@ export default function DetailPanel() {
selectedPrdtId={selectedPrdtId}
selectedIndex={selectedIndex}
setSelectedIndex={setSelectedIndex}
launchedFromPlayer={panelInfo.launchedFromPlayer}
/>
)}
{/* 그룹상품 영역 */}
@@ -194,6 +199,7 @@ export default function DetailPanel() {
selectedPrdtId={selectedPrdtId}
selectedIndex={selectedIndex}
setSelectedIndex={setSelectedIndex}
launchedFromPlayer={panelInfo.launchedFromPlayer}
/>
)}
{/* 구매불가상품 영역 */}
@@ -203,6 +209,7 @@ export default function DetailPanel() {
selectedPrdtId={selectedPrdtId}
selectedIndex={selectedIndex}
setSelectedIndex={setSelectedIndex}
launchedFromPlayer={panelInfo.launchedFromPlayer}
/>
)}
{/* 테마그룹상품 영역*/}
@@ -213,6 +220,7 @@ export default function DetailPanel() {
selectedCurationId={selectedCurationId}
selectedPatnrId={selectedPatnrId}
themeType={themeType}
launchedFromPlayer={panelInfo.launchedFromPlayer}
/>
)}
</TBody>

View File

@@ -21,6 +21,7 @@ export default function UnableProduct({
selectedPrdtId,
selectedIndex,
setSelectedIndex,
launchedFromPlayer
}) {
const productData = useSelector((state) => state.main.productData);
@@ -46,6 +47,7 @@ export default function UnableProduct({
productInfo={productData}
soldoutFlag={soldout}
promotionCode={promotionCode}
launchedFromPlayer={launchedFromPlayer}
/>
<IndicatorOptions
productInfo={productData}

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useState } from "react";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import classNames from "classnames";
import { useDispatch, useSelector } from "react-redux";
@@ -15,6 +15,8 @@ import TVirtualGridList from "../../../../components/TVirtualGridList/TVirtualGr
import useScrollTo from "../../../../hooks/useScrollTo";
import { panel_names } from "../../../../utils/Config";
import css from "./Indicator.module.less";
import { finishVideoPreview, startVideoPlayer } from "../../../../actions/playActions";
import { scaleW } from "../../../../utils/helperMethods";
const Container = SpotlightContainerDecorator(
{ enterTo: "last-focused", preserveld: true },
@@ -22,47 +24,52 @@ const Container = SpotlightContainerDecorator(
);
const SpottableComponent = Spottable("div");
const SpottableImage = Spottable(Image);
const IMAGE_WIDTH = 152;
export default function Indicator({
const IMAGE_WIDTH = scaleW(152);
function Indicator({
selectedIndex,
setSelectedIndex,
productInfo,
soldoutFlag,
launchedFromPlayer,
promotionCode,
}) {
const dispatch = useDispatch();
const [selectedImage, setSelectedImage] = useState(null);
const { cursorVisible } = useSelector((state) => state.common.appStatus);
const { getScrollTo, scrollTop } = useScrollTo();
const [detailVideoPlaying, setDetailVideoPlaying] = useState(!launchedFromPlayer && productInfo.prdtMediaUrl);
let imagePosition = IMAGE_WIDTH * selectedIndex - IMAGE_WIDTH;
let images = [];
console.log("#promotionCode", promotionCode);
useEffect(() => {
if (productInfo?.prdtMediaUrl !== null && selectedIndex > 0) {
setSelectedImage(images[selectedIndex]);
} else {
setSelectedImage(productInfo.imgUrls600[selectedIndex]);
console.log('yhcho productInfo detailVideoPlaying', productInfo, launchedFromPlayer, detailVideoPlaying);
if(detailVideoPlaying && productInfo.prdtMediaUrl){ //auto play
dispatch(startVideoPlayer({
showUrl: productInfo.prdtMediaUrl,
showNm: productInfo.prdtNm,
patnrNm: productInfo.patncNm,
patncLogoPath: productInfo.patncLogoPath,
orderPhnNo: productInfo.orderPhnNo,
shptmBanrTpNm: "MEDIA",
modal: true,
modalContainerId: "indicator_videoContainer", //to calc width, height, left, top
modalClassName: selectedIndex === 0 ? css.videoModal:css.videoModalHide
}));
}
}, [selectedIndex, productInfo]);
}, [selectedIndex, productInfo, detailVideoPlaying, launchedFromPlayer]);
const handleUpClick = useCallback(() => {
if (selectedIndex === 0) {
return;
}
setSelectedIndex((prev) => prev - 1);
if (productInfo.imgUrls600.length - 1 !== selectedIndex) {
if (listImages.length - 1 !== selectedIndex) {
scrollTop({ y: imagePosition - IMAGE_WIDTH, animate: true });
}
}, [selectedIndex]);
}, [selectedIndex, listImages]);
const handleDownClick = useCallback(() => {
if (productInfo.imgUrls600.length - 1 === selectedIndex) {
if (listImages.length.length - 1 === selectedIndex) {
return;
}
setSelectedIndex((prev) => prev + 1);
if (selectedIndex > 1) {
scrollTop({
@@ -70,23 +77,30 @@ export default function Indicator({
animate: true,
});
}
}, [selectedIndex, selectedImage]);
}, [selectedIndex, listImages]);
const canPlayVideo = useMemo(()=>{
return productInfo.prdtMediaUrl && selectedIndex === 0;
},[productInfo, selectedIndex]);
const handleVideoOnClick = useCallback(() => {
dispatch(
pushPanel({
name: panel_names.PLAYER_PANEL,
panelInfo: {
showUrl: productInfo.prdtMediaUrl,
showNm: productInfo.prdtNm,
patnrNm: productInfo.patncNm,
patncLogoPath: productInfo.patncLogoPath,
orderPhnNo: productInfo.orderPhnNo,
shptmBanrTpNm: "MEDIA",
},
})
);
}, [dispatch]);
if(canPlayVideo){
dispatch(startVideoPlayer({
showUrl: productInfo.prdtMediaUrl,
showNm: productInfo.prdtNm,
patnrNm: productInfo.patncNm,
patncLogoPath: productInfo.patncLogoPath,
orderPhnNo: productInfo.orderPhnNo,
shptmBanrTpNm: "MEDIA",
modal: !detailVideoPlaying,
modalContainerId: "indicator_videoContainer", //to calc width, height, left, top
modalClassName:css.videoModal
}));
if(!detailVideoPlaying){
setDetailVideoPlaying(true);
}
}
}, [dispatch, productInfo, detailVideoPlaying, canPlayVideo]);
const onSpotlightRight = useCallback(() => {
const timer = setTimeout(() => {
@@ -104,18 +118,27 @@ export default function Indicator({
const onSpotlightLeft = useCallback(() => {
Spotlight.focus("spotlightId_backBtn");
}, []);
const listImages = useMemo(()=>{
const images = [...productInfo.imgUrls600];
if (productInfo?.prdtMediaUrl !== null) {
images.splice(
0,
0,
productInfo.thumbnailUrl960 !== null
? productInfo.thumbnailUrl960
: defaultImage
);
}
return images;
},[productInfo]);
const selectedImage = useMemo(()=>{
return listImages[selectedIndex];
},[listImages, selectedIndex]);
const renderItem = useCallback(
({ index, ...rest }) => {
images = [...productInfo.imgUrls600];
if (productInfo?.prdtMediaUrl !== null) {
images.splice(
0,
0,
productInfo.thumbnailUrl960 !== null
? productInfo.thumbnailUrl960
: defaultImage
);
}
const handleItemClick = () => {
setSelectedIndex(index);
@@ -124,7 +147,7 @@ export default function Indicator({
return (
<>
<SpottableImage
src={images[index]}
src={listImages[index]}
alt=""
className={classNames(
css.image,
@@ -143,63 +166,34 @@ export default function Indicator({
</>
);
},
[productInfo, selectedIndex, selectedImage]
[listImages, selectedIndex]
);
const renderThumbnail = useCallback(() => {
return (
<div className={css.thumbnailContainer}>
{productInfo && productInfo.prdtMediaUrl !== null ? (
selectedIndex === 0 ? (
<>
<SpottableComponent
className={css.player}
onClick={handleVideoOnClick}
onSpotlightUp={onSpotlightUp}
onSpotlightLeft={onSpotlightLeft}
spotlightId="spotlight_videoThumbnail"
>
<TVideoPlayer
showUrl={productInfo.prdtMediaUrl}
width={560}
height={560}
videoIsPlaying
/>
</SpottableComponent>
{productInfo && productInfo[selectedIndex]?.disclaimer && (
<div className={css.disclaimerContainer}>
<span className={css.icon} />
<div className={css.disclaimer}>
{productInfo?.disclaimer}
</div>
</div>
)}
</>
) : (
<SpottableImage
src={selectedImage}
alt=""
className={classNames(css.thumbnail, soldoutFlag && css.soldout)}
onSpotlightRight={onSpotlightRight}
onSpotlightLeft={onSpotlightLeft}
>
{soldoutFlag && <h3 className={css.soldoutLabel}>SOLD OUT</h3>}
</SpottableImage>
)
) : (
<SpottableImage
src={selectedImage}
<SpottableComponent
spotlightId="indicator_videoContainer"
spotlightDisabled={!canPlayVideo}
disabled={!canPlayVideo}
className={classNames(css.thumbnail, soldoutFlag && css.soldout)}
onSpotlightRight={onSpotlightRight}
onSpotlightLeft={onSpotlightLeft}
onClick={handleVideoOnClick}
>
<>
<Image
className={css.image}
src={detailVideoPlaying && canPlayVideo ? "" : selectedImage}
alt=""
className={classNames(css.thumbnail, soldoutFlag && css.soldout)}
onSpotlightRight={onSpotlightRight}
onSpotlightLeft={onSpotlightLeft}
>
{soldoutFlag && <h3 className={css.soldoutLabel}>SOLD OUT</h3>}
</SpottableImage>
)}
></Image>
{soldoutFlag && <h3 className={css.soldoutLabel}>SOLD OUT</h3>}
{!detailVideoPlaying && canPlayVideo && <h3 className={css.soldoutLabel}>Play Button (todo)</h3>}
</>
</SpottableComponent>
</div>
);
}, [productInfo, selectedIndex, selectedImage, soldoutFlag]);
}, [productInfo, selectedIndex, selectedImage, soldoutFlag, detailVideoPlaying]);
return (
<Container className={css.indicatorContainer}>
@@ -218,10 +212,10 @@ export default function Indicator({
<TVirtualGridList
cbScrollTo={getScrollTo}
className={css.tVirtualGridList}
dataSize={productInfo.imgUrls600.length}
itemWidth={144}
itemHeight={144}
spacing={8}
dataSize={listImages.length}
itemWidth={scaleW(144)}
itemHeight={scaleW(144)}
spacing={scaleW(8)}
renderItem={renderItem}
/>
)}
@@ -231,10 +225,27 @@ export default function Indicator({
spotlightDisabled={!cursorVisible}
className={classNames(
css.downButton,
productInfo.imgUrls600.length - 1 === selectedIndex && css.disable
(listImages.length-1) === selectedIndex && css.disable
)}
/>
</div>
</Container>
);
}
const propsAreEqual = (prev, next) => {
const keys = Object.keys(prev);
const nextKeys = Object.keys(next);
if(keys.length !== nextKeys.length){
return false;
}
for(let i=0; i<keys.length; i++){
if(prev[keys[i]] !== next[keys[i]]){
if (JSON.stringify(prev[keys[i]]) === JSON.stringify(next[keys[i]])) {
continue;
}
return false;
}
}
return true;
}
export default React.memo(Indicator, propsAreEqual);

View File

@@ -76,7 +76,11 @@
position: relative;
display: flex;
justify-content: center;
.image{
position: absolute;
width: 100%;
height: 100%;
}
> h3 {
font-size: 36px;
font-weight: bold;
@@ -231,3 +235,9 @@
//isOptions
}
.videoModal::after {
// .focused(@boxShadow:22px, @borderRadius: 0px);
}
.videoModalHide {
visibility: hidden;
}

View File

@@ -3,7 +3,6 @@ import React, { useCallback, useEffect, useState } from "react";
import classNames from "classnames";
import { useDispatch } from "react-redux";
import Spotlight from "@enact/spotlight";
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
import Spottable from "@enact/spotlight/Spottable";
@@ -14,11 +13,11 @@ import emptyVerImage from "../../../../assets/images/img-home-banner-empty-ver.p
import liveShow from "../../../../assets/images/tag-liveshow.png";
import { pushPanel } from "../../../actions/panelActions";
import CustomImage from "../../../components/CustomImage/CustomImage";
import TVideoPlayer from "../../../components/TVideoPlayer/TVideoPlayer";
import usePriceInfo from "../../../hooks/usePriceInfo";
import useScrollReset from "../../../hooks/useScrollReset";
import { panel_names } from "../../../utils/Config";
import css from "./RandomUnit.module.less";
import { finishVideoPreview, startVideoPlayer } from "../../../actions/playActions";
const SpottableComponent = Spottable("div");
@@ -35,13 +34,11 @@ export default function RandomUnit({
}) {
const bannerDetailInfos = bannerData.bannerDetailInfos;
const [videoIsPlaying, setVideoIsPlaying] = useState(false);
const [randomData, setRandomData] = useState("");
const [priceInfos, setpriceInfos] = useState("");
const [videoIndex, setVideoIndex] = useState([]);
const [videoError, setVideoError] = useState(false);
const [randomNumber, setRandomNumber] = useState("");
const { handleScrollReset, handleStopScrolling } =
useScrollReset(scrollTopBody);
@@ -86,14 +83,28 @@ export default function RandomUnit({
};
// 포커스 인
const onFocus = () => {
setVideoIsPlaying(true);
const onFocus = (ev) => {
handleScrollReset();
if(randomData.shptmBanrTpNm == "LIVE" ||
randomData.shptmBanrTpNm == "VOD"){
dispatch(startVideoPlayer({
patnrId: randomData.patnrId,
showId: randomData.showId,
shptmBanrTpNm: randomData.shptmBanrTpNm,
lgCatCd: randomData.lgCatCd,
modal: true,
modalContainerId: spotlightId, //to calc width, height, left, top
modalClassName:css.videoModal
}));
}
};
// 포커스 아웃
const onBlur = () => {
setVideoIsPlaying(false);
const onBlur = (ev) => {
if(randomData.shptmBanrTpNm == "LIVE" ||
randomData.shptmBanrTpNm == "VOD"){
dispatch(finishVideoPreview());
}
handleStopScrolling();
};
@@ -119,17 +130,13 @@ export default function RandomUnit({
} else if (randomData.shptmLnkTpNm === "Product Detail") {
panelName = panel_names.DETAIL_PANEL;
} else if (randomData.shptmLnkTpNm === "VOD") {
return dispatch(
pushPanel({
name: panel_names.PLAYER_PANEL,
panelInfo: {
return dispatch(startVideoPlayer({
patnrId: randomData.patnrId,
showId: randomData.showId,
shptmBanrTpNm: "VOD",
lgCatCd: randomData.lgCatCd,
},
})
);
modal: false
}));
} else if (randomData.shptmLnkTpNm === "Show Detail") {
panelName = panel_names.HOME_PANEL;
} else if (randomData.shptmLnkTpNm === "Theme Page") {
@@ -166,19 +173,17 @@ export default function RandomUnit({
};
// 비디오 클릭
const videoClick = () => {
dispatch(
pushPanel({
name: panel_names.PLAYER_PANEL,
panelInfo: {
patnrId: randomData.patnrId,
const videoClick = useCallback(() => {
dispatch(startVideoPlayer({
patnrId: randomData.patnrId,
showId: randomData.showId,
shptmBanrTpNm: randomData.shptmBanrTpNm,
lgCatCd: randomData.lgCatCd,
},
})
);
};
modal: false,
modalContainerId: spotlightId, //to calc width, height, left, top
modalClassName:css.videoModal
}));
},[randomData, spotlightId]);
// 투데이즈 딜 가격 정보
const { originalPrice, discountedPrice, discountRate, offerInfo } =
@@ -260,72 +265,40 @@ export default function RandomUnit({
</p>
) : null}
{/* 비디오 오류, 비디오 포커스 상태, 라이브 일 경우 */}
{videoError === true &&
videoIsPlaying === true &&
randomData.shptmBanrTpNm === "LIVE" ? (
<div className={css.errorContents}>
<div>
<CustomImage
delay={0}
className={css.errorlogo}
src={randomData.patncLogoPath}
animationSpeed="fast"
/>
<p className={css.errorText}>
Click the screen to see more products!
</p>
</div>
</div>
) : null}
{videoIsPlaying === false ? (
<div
className={classNames(
css.itemBox,
isHorizontal && css.isHorizontal
)}
>
{/* {randomData.tmnlImgPath !== null ? (
<img src={randomData.tmnlImgPath} />
) : null} */}
{randomData.tmnlImgPath == null ? (
<CustomImage
delay={0}
src={
randomData.vtctpYn === "Y" ? emptyVerImage : emptyHorImage
}
animationSpeed="fast"
/>
) : (
<CustomImage
delay={0}
src={randomData.tmnlImgPath}
animationSpeed="fast"
/>
)}
</div>
) : (
<TVideoPlayer
showUrl={randomData.showUrl}
className={""}
videoIsPlaying={videoIsPlaying}
width={randomData.vtctpYn === "Y" ? 486 : 744}
height={randomData.vtctpYn === "Y" ? 858 : 420}
videoErrorCheck={videoErrorCheck}
videoType={randomData.shptmBanrTpNm}
/>
)}
{videoError === false ? (
<p className={css.brandIcon}>
{/* 비디오 오류, --> Player 내부로 이동 */}
<div
className={classNames(
css.itemBox,
isHorizontal && css.isHorizontal
)}
>
{/* {randomData.tmnlImgPath !== null ? (
<img src={randomData.tmnlImgPath} />
) : null} */}
{randomData.tmnlImgPath == null ? (
<CustomImage
delay={0}
src={randomData.patncLogoPath}
src={
randomData.vtctpYn === "Y" ? emptyVerImage : emptyHorImage
}
animationSpeed="fast"
/>
</p>
) : null}
) : (
<CustomImage
delay={0}
src={randomData.tmnlImgPath}
animationSpeed="fast"
/>
)}
</div>
<p className={css.brandIcon}>
<CustomImage
delay={0}
src={randomData.patncLogoPath}
animationSpeed="fast"
/>
</p>
</SpottableComponent>
) : randomData.shptmBanrTpNm == "Today's Deals" ? (
<SpottableComponent

View File

@@ -185,3 +185,6 @@
}
}
}
.videoModal::after {
.focused(@boxShadow:22px, @borderRadius: 12px);
}

View File

@@ -69,8 +69,8 @@ export default function MainView() {
const [tabActivated, setTabActivated] = useState(false);
const isOnTop = useMemo(() => {
return !mainIndex && panels.length <= 0;
}, [mainIndex, panels.length]);
return !mainIndex && (panels.length <= 0 || (panels.length===1 && panels[0].panelInfo.modal));
}, [mainIndex, panels]);
const onPreImageLoadComplete = useCallback(() => {
console.log("MainView onPreImageLoadComplete");
@@ -79,20 +79,49 @@ export default function MainView() {
const renderTopPanel = useCallback(() => {
if (panels && panels.length > 0) {
let renderringPanels = [];
if(panels[panels.length-1]?.name === Config.panel_names.PLAYER_PANEL ||
panels[panels.length-2]?.name === Config.panel_names.PLAYER_PANEL
){
renderringPanels = panels.slice(-2);
}else{
renderringPanels = panels.slice(-1);
}
return (
<>
{isOnTop &&
<HomePanel isOnTop={isOnTop} />
}
{renderringPanels.map((panel, index) => {
const Component = panelMap[panel.name];
//render last two panels if there's videoplayer
return <Component
key={panel.name+(panels.indexOf(panel))}//to maintain react virtual dom
panelInfo={panel.panelInfo}
spotlightId={panel.name}
isTabActivated={tabActivated}
isOnTop={index===(renderringPanels.length-1)}
/>
})}
</>
);
/*
const panel = panels[panels.length - 1];
const Component = panelMap[panel.name];
return (
<Component
panelInfo={panel.panelInfo}
spotlightId={panel.name}
isTabActivated={tabActivated}
/>
<Component
panelInfo={panel.panelInfo}
spotlightId={panel.name}
isTabActivated={tabActivated}
/>
);
} else {
return null;
*/
} else if(isOnTop){
return <HomePanel isOnTop={isOnTop} />;
}
}, [panels]);
return null;
}, [panels, tabActivated, isOnTop]);
const onTabActivated = useCallback((activated) => {
setTabActivated(activated);
@@ -100,7 +129,12 @@ export default function MainView() {
const topPanelName = useMemo(() => {
if (panels && panels.length > 0) {
return panels[panels.length - 1].name;
let targetName = panels[panels.length - 1].name;
if(panels[panels.length - 1].name === Config.panel_names.PLAYER_PANEL
&& panels[panels.length - 1].panelInfo.modal){
targetName = panels[panels.length - 2]?.name;
}
return targetName;
}
return null;
@@ -108,9 +142,14 @@ export default function MainView() {
useEffect(() => {
if (panels && panels.length > 0) {
const panel = panels[panels.length - 1];
Spotlight.focus(panel.name);
let panel = panels[panels.length - 1];
if(panels[panels.length - 1].name === Config.panel_names.PLAYER_PANEL
&& panels[panels.length - 1].panelInfo.modal){
panel = panels[panels.length - 2];
}
if(panel?.name){
Spotlight.focus(panel?.name);
}
}
}, [panels]);
@@ -155,11 +194,9 @@ export default function MainView() {
showLoadingPanel.type === "launching" ? css.transparent : null
)}
>
{isOnTop ? (
<HomePanel isOnTop={isOnTop} />
) : (
{
<div className={classNames(css.mainlayout)}>{renderTopPanel()}</div>
)}
}
<TabLayout
topPanelName={topPanelName}
onTabActivated={onTabActivated}

View File

@@ -7,8 +7,8 @@
display: flex;
flex-direction: row-reverse;
justify-content: flex-end;
width: 100%;
height: 100%;
width: 100vw;
height: 100vh;
background-color: #fff;
}
}

View File

@@ -16,9 +16,9 @@ export default function PlayerOverlayHeader({
onClick,
setSelectedIndex,
}) {
const onClickBack = () => {
const onClickBack = (ev) => {
if (onClick) {
onClick();
onClick(ev);
}
};

View File

@@ -24,7 +24,7 @@ import {
} from "../../actions/mainActions";
import * as PanelActions from "../../actions/panelActions";
import { updatePanel } from "../../actions/panelActions";
import { getChatLog, getSubTitle } from "../../actions/playActions";
import { getChatLog, getSubTitle, startVideoPlayer } from "../../actions/playActions";
import { MediaControls } from "../../components/MediaPlayer";
import TButton from "../../components/TButton/TButton";
import TButtonTab, { LIST_TYPE } from "../../components/TButtonTab/TButtonTab";
@@ -53,12 +53,12 @@ const unableToPlay = new Job((callback) => {
// callback();
}, 5000);
const PlayerPanel = ({ hideChildren, isTabActivated, ...props }) => {
const PlayerPanel = ({ hideChildren, isTabActivated, panelInfo, isOnTop, ...props }) => {
const dispatch = useDispatch();
const videoPlayer = useRef(null);
const [playListInfo, setPlayListInfo] = useState("");
const [shopNowInfo, setShopNowInfo] = useState();
const [panelInfo, setPaneInfo] = useState([]);
const [modalStyle, setModalStyle] = useState({});
const [tab, setTab] = useState(0);
const [sideOpen, setSideOpen] = useState(true);
const [selectedIndex, setSelectedIndex] = useState(0);
@@ -81,22 +81,27 @@ const PlayerPanel = ({ hideChildren, isTabActivated, ...props }) => {
$L(panelInfo?.shptmBanrTpNm === "LIVE" ? "LIVE CHANNEL" : "FEATURED SHOWS"),
];
// 패널 정보 받기
const getPanelInfo = useCallback(() => {
if (panels) {
for (let i = 0; i < panels.length; i++) {
if (panels[i].name === "playerpanel") {
setPaneInfo(panels[i].panelInfo);
}
}
}
}, [panels]);
const onClickBack = useCallback(
(ev) => {
dispatch(PanelActions.popPanel());
console.log('yhcho onClickBack', panelInfo);
//modal로부터 Full 전환된 경우 다시 preview 모드로 돌아감.
if(panelInfo.modalContainerId && !panelInfo.modal){
dispatch(startVideoPlayer({
...panelInfo,
modal: true,
}));
videoPlayer.current?.hideControls();
ev.stopPropagation();
ev.preventDefault();
return;
}
if(!panelInfo.modal){
dispatch(PanelActions.popPanel());
ev.stopPropagation();
ev.preventDefault();
}
},
[dispatch]
[dispatch, panelInfo]
);
const handleItemClick = useCallback(
@@ -127,11 +132,6 @@ const PlayerPanel = ({ hideChildren, isTabActivated, ...props }) => {
];
setPlayListInfo(updatedPlayListInfo);
};
useEffect(() => {
getPanelInfo();
}, [panels, panelInfo, dispatch]);
useEffect(() => {
if (panelInfo) {
dispatch(
@@ -160,7 +160,7 @@ const PlayerPanel = ({ hideChildren, isTabActivated, ...props }) => {
})
);
}
}, [dispatch, panelInfo, selectedIndex, panels]);
}, [dispatch, panelInfo, selectedIndex]);
useEffect(() => {
if (panelInfo?.shptmBanrTpNm === "LIVE") {
@@ -172,7 +172,7 @@ const PlayerPanel = ({ hideChildren, isTabActivated, ...props }) => {
}
}
}
}, [panelInfo, playListInfo]);
}, [panelInfo?.shptmBanrTpNm, playListInfo]);
useEffect(() => {
if (
@@ -246,45 +246,77 @@ const PlayerPanel = ({ hideChildren, isTabActivated, ...props }) => {
}, [isActive]);
useEffect(() => {
if (panelInfo && shopNowInfo) {
Spotlight.focus("playVideoShopNowBox");
Spotlight.focus(`spotlightId-video-${panelInfo.index}`);
return;
if(!panelInfo.modal){
if (panelInfo && shopNowInfo) {
Spotlight.focus("playVideoShopNowBox");
Spotlight.focus(`spotlightId-video-${panelInfo.index}`);
return;
}
Spotlight.focus("hotpicks-close-arrow");
}
Spotlight.focus("hotpicks-close-arrow");
}, [panelInfo, shopNowInfo, playListInfo]);
useEffect(() => {
if(panelInfo.modal && panelInfo.modalContainerId){
const node = document.querySelector(`[data-spotlight-id="${panelInfo.modalContainerId}"]`);
if(node){
const {width, height, top, left} = node.getBoundingClientRect();
const modalStyle = {width: width+'px', height: height+'px', top: top+'px', left: left+'px', position: 'fixed'};
setModalStyle(modalStyle);
dispatch(updatePanel({name: panel_names.PLAYER_PANEL, panelInfo: {modalStyle: modalStyle}}));
}else{
setModalStyle(panelInfo.modalStyle);
console.error('PlayerPanel modalContainerId node not found', panelInfo.modalContainerId);
}
}else if(isOnTop && !panelInfo.modal && videoPlayer.current?.getMediaState()?.paused){
videoPlayer.current.play();
}
}, [panelInfo, isOnTop]);
const cannotPlay = useMemo(()=>{
const topPanel = panels[panels.length-1];
return !isOnTop && topPanel?.name === panel_names.PLAYER_PANEL;
},[panels, isOnTop]);
delete props.panelInfo;
const getPlayer = useCallback((ref) => {
videoPlayer.current = ref;
}, []);
return (
<TPanel isTabActivated={false} {...props} className={css.videoContainer}>
<VideoPlayer
// setApiProvider={getPlayer}
onEnded={onClickBack} // 플레이어가 끝날때 호출
noAutoPlay={false} // 오토플레이
autoCloseTimeout={100000000000000} // 컨트롤 버튼 숨기는 시간
playListInfo={playListInfo && playListInfo[selectedIndex]} // 비디오 데이터
onBackButton={onClickBack} // 뒤로가기 버튼
panelInfo={panelInfo} // 패널정보 (라이브,VOD)
setSelectedIndex={setSelectedIndex} // 선택한 인덱스 Set
selectedIndex={selectedIndex} // 선택한 인덱스
spotlightDisabled={sideOpen}
src={playListInfo && playListInfo[selectedIndex]?.showUrl}
>
<source
<TPanel isTabActivated={false} {...props} className={classNames(css.videoContainer, panelInfo.modal && css.modal, !isOnTop && css.background)} handleCancel={onClickBack}>
<VideoPlayer
setApiProvider={getPlayer}
disabled={panelInfo.modal}
onEnded={onClickBack} // 플레이어가 끝날때 호출
noAutoPlay={cannotPlay} // 오토플레이
autoCloseTimeout={100000000000000} // 컨트롤 버튼 숨기는 시간
playListInfo={playListInfo && playListInfo[selectedIndex]} // 비디오 데이터
onBackButton={onClickBack} // 뒤로가기 버튼
panelInfo={panelInfo} // 패널정보 (라이브,VOD)
setSelectedIndex={setSelectedIndex} // 선택한 인덱스 Set
selectedIndex={selectedIndex} // 선택한 인덱스
spotlightDisabled={sideOpen || panelInfo.modal}
src={playListInfo && playListInfo[selectedIndex]?.showUrl}
type="application/mpegurl"
/>
<track
kind="subtitles"
src={vodSubtitleData && vodSubtitleData}
srclang="en"
label="English"
type="text/vtt"
default
/>
{/* 테스트 */}
{/* <source
style={panelInfo.modal ? modalStyle : {}}
modalClassName={panelInfo.modal && panelInfo.modalClassName}
>
<source
src={playListInfo && playListInfo[selectedIndex]?.showUrl}
type="application/mpegurl"
/>
{/* <track
kind="subtitles"
src={vodSubtitleData && vodSubtitleData}
srclang="en"
label="English"
type="text/vtt"
default
/> */}
{/* 테스트 */}
{/* <source
src={
" https://d130labfjte6iy.cloudfront.net/vod/1e62b39b-4301-4fe5-a16e-bebebef4e1c6/3e72500af7fc4d9d800e7a9533a29a36.mp4"
}
@@ -297,9 +329,9 @@ const PlayerPanel = ({ hideChildren, isTabActivated, ...props }) => {
label="English"
type="text/vtt"
/> */}
</VideoPlayer>
</VideoPlayer>
{/* 리액트 플레이어 테스트용 */}
{typeof window === "object" && window.PalmSystem ? (
{/* {typeof window === "object" && window.PalmSystem ? (
""
) : (
<>
@@ -314,7 +346,7 @@ const PlayerPanel = ({ hideChildren, isTabActivated, ...props }) => {
<SpottableBtn className={css.videoReduce} onClick={onClickBack} />
</div>
</>
)}
)} */}
{playListInfo && playListInfo[selectedIndex]?.orderPhnNo && (
<VideoOverlayWithPhoneNumber
className={css.videoOverlayWithPhoneNumber}
@@ -362,7 +394,6 @@ const PlayerPanel = ({ hideChildren, isTabActivated, ...props }) => {
<LiveChannelContents
setSelectedIndex={setSelectedIndex}
liveInfos={playListInfo}
setPaneInfo={setPaneInfo}
/>
)}
@@ -375,4 +406,20 @@ const PlayerPanel = ({ hideChildren, isTabActivated, ...props }) => {
);
};
export default PlayerPanel;
const propsAreEqual = (prev, next) => {
const keys = Object.keys(prev);
const nextKeys = Object.keys(next);
if(keys.length !== nextKeys.length){
return false;
}
for(let i=0; i<keys.length; i++){
if(prev[keys[i]] !== next[keys[i]]){
if (JSON.stringify(prev[keys[i]]) === JSON.stringify(next[keys[i]])) {
continue;
}
return false;
}
}
return true;
}
export default React.memo(PlayerPanel, propsAreEqual);

View File

@@ -1,9 +1,10 @@
@import "../../style/CommonStyle.module.less";
@import "../../style/utils.module.less";
@videoBackgroundColor: black;
.videoContainer {
position: relative;
background-color: @videoBackgroundColor;
.playButton {
.size(@w: 60px, @h: 60px);
background-image: url("../../../assets/images/btn/btn-video-play-nor@3x.png");
@@ -156,4 +157,19 @@
border-radius: 50%;
}
}
&.modal,
&.background{
width:1px;
height: 1px;
left: -1px;
top: -1px;
pointer-events: none;
background-color: @videoBackgroundColor;
.videoOverlayWithPhoneNumber,
.tabContainer,
.arrow,
.toOpenBtn{
display: none;
}
}
}

View File

@@ -41,6 +41,7 @@ export default function ShopNowContents({ shopNowInfo }) {
panelInfo: {
patnrId,
prdtId,
launchedFromPlayer: true
},
})
);