일반공격이 전체공격에 2회 공격인 유튜브는 좋아하세요?
단일 동영상과 플레이리스트를 모두 사용해보자React와 Next.js에서는 react-youtube
를 사용하여 YouTube iFrame API
를 사용할 수 있습니다.
이번 포스팅에서는 react-youtube 패키지를 사용하여 유튜브 동영상을 가져오는 방법을 제시합니다.
플레이어 컴포넌트
import React, { useRef } from 'react';
import styled from '@emotion/styled';
import YouTube, { YouTubeProps } from 'react-youtube';
import styles from '@/styles/watch.module.sass';
import { rem } from '@/styles/designSystem';
interface Props {
videoId: string;
isPlaylist?: boolean;
titles?: string;
}
const Controllers = styled.div({
'& strong': {
display: 'block',
padding: `${rem(5)} ${rem(15)}`,
fontSize: rem(14),
fontWeight: '700',
color: 'var(--default-text)',
},
'& button': {
backgroundColor: 'transparent',
width: '100%',
padding: `${rem(1)} ${rem(15)}`,
textAlign: 'left',
'& span': {
maxWidth: '100%',
padding: `${rem(2)} 0`,
display: 'inline-block',
borderBottom: '1px solid var(--border)',
color: 'var(--txt-subject)',
fontSize: rem(12),
lineHeight: 1.5,
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
},
},
});
const YouTubePlayer = ({ videoId, isPlaylist, titles }: Props) => {
const playerRef = useRef<any>(null);
const opts: YouTubeProps['opts'] = {
width: 560,
height: 315,
playerVars: {
autoplay: 1,
modestbranding: 1,
rel: 0,
},
};
const playList: YouTubeProps['opts'] = {
width: 560,
height: 315,
playerVars: {
autoplay: 1,
rel: 0,
playlist: videoId,
loop: 1,
},
};
const onReady = (event: any) => {
playerRef.current = event.target;
};
const handleChangeVideo = (videoId: string) => {
if (playerRef.current) {
playerRef.current.loadVideoById(videoId);
}
};
const videoIdsArray = videoId ? videoId.split(',') : [];
const titlesArray = titles ? titles.split(',') : [];
return (
<>
{isPlaylist ? (
<>
<YouTube videoId={videoId} opts={playList} onReady={onReady} />
<Controllers className={styles.controller}>
<strong>수동으로 영상 넘기기</strong>
{videoIdsArray.map((id, index) => (
<button key={id} onClick={() => handleChangeVideo(id)}>
<span>
{index + 1}. {titlesArray[index]}
</span>
</button>
))}
</Controllers>
</>
) : (
<YouTube videoId={videoId} opts={opts} />
)}
</>
);
};
export default YouTubePlayer;
@/styles/designSystem
은 스타일링 컴포넌트입니다. 스타일링에 대한 것은 맨 마지막에 추가로 설명드리겠습니다.
isPlayList
가 true
값을 가진다면 플레이리스트 방식으로 재생되고, false
갑을 가지고 있거나, 생략이 된다면 <YouTube videoId={videoId} opts={opts} />
이 코드가 작동합니다. 이 경우에는 비디오 아이디값과 옵션값만 적용하여 작동됩니다.
Props
interface에서 videoId
는 필수값이고, isPlaylist, titles는 플레이리스트 일 때만 받아와야 하기 때문에 필수가 아닙니다.
컨트롤러 컴포넌트
import Image from 'next/image';
import styled from '@emotion/styled';
import React, { useState } from 'react';
import { isDesktop } from 'react-device-detect';
import { mixIn, rem } from '@/styles/designSystem';
import { images } from './images';
import YouTubePlayer from './YouTubePlayer';
import styles from '@/styles/watch.module.sass';
interface Props {
videoId: string;
isPlaylist?: boolean;
titles?: string;
}
const Container = styled.div<{ isDesktop?: boolean }>(({ isDesktop }) => ({
position: 'relative',
overflow: 'hidden',
'&:hover img': {
transform: isDesktop ? 'scale(1.02)' : undefined,
},
'& img': {
transition: 'all .4s cubic-bezier(.4,0,.2,1)',
display: 'block',
aspectRatio: '1920 / 1080',
width: '100%',
height: 'auto',
objectFit: 'cover',
...mixIn.imageRendering,
},
'& > button': {
display: 'flex',
position: 'absolute',
top: 0,
left: 0,
background: 'none',
justifyContent: 'center',
alignItems: 'center',
border: 0,
width: '100%',
height: '100%',
'&:hover i': {
opacity: isDesktop ? 1 : undefined,
},
'& i': {
transition: 'all .4s cubic-bezier(.4,0,.2,1)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'rgba(244, 246, 250, .7)',
opacity: isDesktop ? 0 : undefined,
borderRadius: rem(52),
width: rem(52),
height: rem(52),
'&::before': {
content: "''",
display: 'block',
width: rem(36),
height: rem(36),
background: `url(${images.misc.play}) no-repeat 50% 50%/contain`,
},
},
'& span': {
...mixIn.screenReaderOnly,
},
},
'& div': {
width: '100%',
height: '100%',
},
'& iframe': {
border: 0,
aspectRatio: '1920 / 1080',
width: '100%',
height: 'auto',
},
}));
const YouTubeController = ({ videoId, isPlaylist, titles }: Props) => {
const [isPlaying, setIsPlaying] = useState(false);
const handlePlay = () => {
setIsPlaying(true);
};
return (
<Container isDesktop={isDesktop} className={isPlaying && isPlaylist ? `${styles['youtube-playlist']}` : ''}>
{!isPlaying ? (
<>
{isPlaylist ? (
<Image
src={`https://i.ytimg.com/vi_webp/${videoId.split(',')[0]}/hqdefault.webp`}
width={640}
height={480}
unoptimized
priority
alt=""
/>
) : (
<Image
src={`https://i.ytimg.com/vi_webp/${videoId}/hqdefault.webp`}
width={640}
height={480}
unoptimized
priority
alt=""
/>
)}
<button type="button" onClick={handlePlay}>
<i />
<span>영상 재생하기</span>
</button>
</>
) : (
<YouTubePlayer videoId={videoId} isPlaylist={isPlaylist} titles={titles} />
)}
</Container>
);
};
export default YouTubeController;
Props
interface는 컨트롤러에서도 사용되고 플레이어에서도 사용되기 때문에 따로 파일을 만들어export interface Props
를 만들어 가져와 사용 가능합니다.
handlePlay
핸들러를 누르면 재생이 되고, 누르지 않는다면 videoId
의 첫 번째 썸네일이 기본 썸네일 상태로 렌더링됩니다.
물론, 플레이리스트가 아닌 경우에는 videoId
에 맞는 썸네일을 자동으로 가져옵니다.
플레이리스트인 경우, 타이틀 값들은 titles
에서 가져옵니다.
렌더링 화면
공통사항
import YouTubeController from '@/components/YouTubeController';
단일 영상인 경우
<YouTubeController videoId={data.video_id} isPlaylist={false} />
// 또는
```tsx
<YouTubeController videoId={data.video_id} />
data.video_id
는 API 또는 JSON에서 저장된 값입니다.
플레이리스트인 경우
<YouTubeController videoId={data.video_ids} titles={data.titles} isPlaylist={true} />
titles
와 isPlaylist
를 받아서 YouTubeController에 넘깁니다.
videoId
와 titles
는 여러개의 값을 ','(콤마)로 구분해서 받아오면 됩니다.
이를테면 A,B,C 이렇게요.
콤마로 구분하기 때문에 영상 제목에 콤마가 들어가면 오작동 합니다. 주의하세요.
designSystem
코드는 **여기**를 확인해 주세요