O612의 데브런닷 스튜디오

일반공격이 전체공격에 2회 공격인 유튜브는 좋아하세요?

단일 동영상과 플레이리스트를 모두 사용해보자
수정
featured image thumbnail for post 일반공격이 전체공격에 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은 스타일링 컴포넌트입니다. 스타일링에 대한 것은 맨 마지막에 추가로 설명드리겠습니다.

isPlayListtrue값을 가진다면 플레이리스트 방식으로 재생되고, 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} />

titlesisPlaylist를 받아서 YouTubeController에 넘깁니다.

videoIdtitles는 여러개의 값을 ','(콤마)로 구분해서 받아오면 됩니다.

이를테면 A,B,C 이렇게요.

콤마로 구분하기 때문에 영상 제목에 콤마가 들어가면 오작동 합니다. 주의하세요.


designSystem 코드는 **여기**를 확인해 주세요

FIN!

Copyright 2022 O612 DEV1L.studio develog w/ Devilish code