React | 스트리밍을 이용한 넷플릭스 클론코딩

kim seung chan·2021년 10월 3일
74

Project

https://github.com/tmdckszm/24-2nd-FX-frontend

Project overview

첫 프로젝트가 끝난 후 하루 이틀 정도 재정비 시간을 가지고 두 번째 프로젝트를 시작하게 되었다. 이번 사이트는 저번 블로그 글에서 소개하였던 영상 스트리밍을 이용하여 넷플릭스 사이트를 만들어 보았다. 처음 사이트를 살펴보려고 했을 때 결제를 해야 지만 볼 수 있어서 첫날 당황했던 기억이 있었다. 결제 후 본 넷플릭스 사이트는 전체적인 어려운 레이아웃은 없었지만 모달창을 어떻게 만들지 동영상을 어떻게 넣고 실행시킬지에 대한 고민이 많았다. 첫 프로젝트와 다르게 두번째 프로젝트는 어떠한 소통을 해야 하는지 어떤 것을 협의 해야 될지 경험을 통해 알고 있었기 때문에 소통에 전혀 어려움이 없었다.새로운 기술이었는데도 포기하지 않고 끝까지 멋진 프로젝트를 마무리해준 팀원들에게 감사하며 자세한 기술 내용과 어떤 소통을 했는지 블로그 밑에서 설명을 자세히 하려고 한다.

React와 스트리밍을 이용한 넷플릭스

이번 프로젝트에서는 React Hooksstyled-component를 이용하여 클론사이트를 만들어 보았다. 저번 프로젝트에서는 리액트를 왜 사용하는가에 대해 느꼈다면 이번 프로젝트에서는 hooks에 편리함과 styled-component의 사용이유에 대해 느끼게 되었다. hooks를 사용함으로써 클래스형 컴포넌트와 비교하면 선언하기가 간편하였고 메모리 할당이 많은 스트리밍에는 메모리 자원을 클래스형 컴포넌트 보다는 덜 사용하는 hooks의 기술이 적합하였고 styled-component를 통해 props로 직접 스타일들에 관여할 수 있는 부분은 엄청나게 편리하였다.

  • 클래스형 컴포넌트에 비해 선언하기가 간편하여 주로 화면을 그리는 역할을 한다.
  • 메모리 자원을 클래스형 컴포넌트 보다는 덜 사용한다.
  • 로직의 재사용 가능, 관리가 쉽다.
  • 로직을 한 곳으로 모을수 있어서 가독성이 좋다. (API를 한곳에 모을 수 있다.)

작업기간

2021.09.13 ~ 2021.10.01

기술 스택

프론트엔드 3명

  • HTML/CSS
  • JavaScript
  • React with Hooks
  • React Router
  • Styled-Component
  • Postman
  • AWS

백엔드 3명

  • Django
  • Python
  • Postman
  • MySQL
  • Amazon S3
  • AWS

협업도구

  • git
  • github
  • Trello
  • Slack
  • Notion
  • Zoom

주요 구현 사항

💡 하이라이트 => 내가 구현한 기능 & 참여한 기능

  • access token & 소셜 로그인 회원가입 기능
  • Main 페이지 구성
  • Portal를 이용한 modal창 구성
  • 나의 위시리스트 기능 구현
  • 위시리스트 모달창 구현
  • 데이터 갯수에 따른 캐러셀 구성
  • Main 창 setInterval를 활용한 동영상 재생
  • Nav bar 구현
  • footer 구현
  • Nav bar 를 이용한 동적 라우팅 구현
  • 스트리밍 구현
  • modal창 스트리밍 연결
  • modal창 리스트 불러오기
  • access token를 활용한 Router 이동
  • Aws 배포

1. Main 화면

setInterval을 통하여 main 화면을 전환했다.

main 화면을 setInterval로 변환시키고 스트리밍을 통해 동영상을 보여주고 있다.

2. Main 화면 스트리밍

Network 탭을 통하여 확인해보면 데이터가 차례차례 들어오는 것을 볼 수 있다.

스트리밍 같은 경우 기억에 남을 코드에 적을 예정이지만 정말 생소한 기술이여서 많은 어려움이 있었다.

3. 네브바 라우터를 통한 화면 전환

네브바를 통한 라우터 구현은 id 값이 아닌 카테고리별로 묶여 있어 어떻게 연결해야 할지 고민이 많았고 TV 프로그램과 영화는 같은 component를 사용하고 있어 re-render가 안되는 문제가 있었다. 이 부분도 밑에서 코드로 설명을 하려고 한다.

4. 포탈을 통한 모달창 및 라우터 연결

이 부분도 생각보다는 쉽지 않은 작업이었다. 밑에 정보와 리스트 같은 경우에는 팀원 분인 동희님이 연결하였고 나는 스트리밍을 연결하였는데 데이터가 id 값이 아닌 한 번 더 들어간 detail_id로 end-point가 되어있었다.

이 부분 같은 경우에는 Optional chaining을 통해 마우스를 올리면 Detail_id 값을 찾고 마우스를 클릭하면 Detail_id 값이 fetch 되도록 만들었다. 자세한 부분은 git hub통해 볼 수 있다.

5. 모달창 스트리밍

이 부분도 main창과 동일한 형식으로 만들었고 스트리밍으로 만들었다.

6. scroll에 따른 nav바 변환

nav bar 변환같은 경우 scrolls시 최대한 render 덜 될 수 있도록 노력하였다.

7. 내가 찜한 리스트

이 부분은 팀원 모두의 코드가 들어가있다. 모달창은 어떻게 다시 열고 라우터는 어떻게 연결할지에 대한 고민이 많이 있었다.

8. 캐러셀

9. 소셜 로그인

기억하고 싶은 코드

추석이 사라진 스트리밍

넷플릭스에 들어갔을 때 제일 먼저 한 생각이 동영상 데이터로 어떻게 받아야 할지 였다. 처음에는 유튜브 링크로 영화를 연결하게 하면 되지 않겠느냐는 생각을 했는데 그게 과연 동영상 서비스를 만드는 취지에 맞을까? 라는 생각이 들었다. 결국, 팀원들 협의한 끝에 스트리밍 서비스를 만들기로 하였다.

스트리밍 부분 백엔드와 소통

백엔드 팀원인 준영님과 어떻게 데이터를 받아야 하고 어떤 방식으로 프론트에서 요청을 주면 되는지 정말 많이 고민하였고 바이트 단위로 데이터를 쪼개서 프론트에 주면 그것을 프론트에서는 Codec 파일과 mediasource 통해 바이트 단위 데이터를 받고 요청을 보낼 때는 바이트 단위로 요청을 보내기로 하였다. 이 부분에서도 팀원들과 구글링도 많이 해보면서 하루 이상이 걸렸던 거 같다.

스트리밍 코드

useEffect(() => {
    const video = videoRef.current;
    const chunks = 30;
    const streamingFile = "url";
    const mediaSource = new MediaSource();

    mediaSource.addEventListener("sourceopen", e => {
      const ms = mediaSource.addSourceBuffer(
        'video/mp4; codecs="avc1.4D4001,mp4a.40.2"'
      );
      ms.addEventListener(
        "updateend",
        () => {
          if (i === chunks - 1) {
            return;
          }
          loadChunk(++i);
        },
        false
      );
      loadChunk(0);
    });

    mediaSource.addEventListener("sourceclose", () => {
      console.log("ended");
    });

    video.src = window.URL.createObjectURL(mediaSource);
  

일단 chunks로 파일을 데이터을 얼마나 주고받을 것인지 정한 후 mediasource 객체를 통해 받은 데이터를 열어준다. 여기서 addSourceBuffer를 사용하는데 여기서 어떤 형식의 파일인지 Codec 정보는 무엇인지 넣어줄 수 있다. (이 부분은 backend와 소통을 잘 해야 하고 codecs 같은 경우에는 사이트에 호환되는 codecs를 넣어주어야 한다.) 후에는 addEvevenListener를 사용하고 loadChunk를 함수를 통해 chunks를 요청한다. loadChunk 함수 같은 경우에는 이어서 설명을 하겠다.

const loadChunk = async i => {
      const start = 0;
      const length = 30000000;

      const chunkSize = Math.ceil(length / chunks);
      const startByte = parseInt(start + chunkSize * i);

      const range =
        "bytes=" + start + chunkSize * i + "-" + (startByte + chunkSize - 1);

      const res = await fetch(streamingFile, {
        headers: {
          Range: range,
        },
      }).then(res => res.arrayBuffer());

      mediaSource.sourceBuffers[0].appendBuffer(new Uint8Array(res));
    };

loadChunk 같은 경우에는 start를 정해주고 chunkSize와 startByte를 정해주고 요청을 보낼 range를 보내준다. 그 후 데이터를 arrayBuffer를 통해 바이트 데이터를 받고 sourceBuffer를 통해 mediasource를 열어준다.

기억하고싶은 코드 2

네브바를 통한 라우터 구현은 TV 프로그램과 영화를 한 component를 사용하여 data fetch를 다시 시켜줘야 하는데 그러지 못한 문제가 있었다.

라우터 코드

useEffect(() => {
    if (
      match.params.genreCategory === "drama" ||
      match.params.genreCategory === "movie"
    ) {
      handleFetch(
        `${props.url}&category=${match.params.genreCategory}`,
        updateCardsData
      );
    } else if (match.params.genreCategory === undefined) {
      handleFetch(`${props.url}`, updateCardsData);
    }
  }, [match.params.genreCategory]);

이 부분은 if을 통해 math.params 별로 rendering을 시켜 해결하였다. fetch 코드로 if 문을 사용할 수 있다는 부분에서 인상 깊었던 코드였다.

Project Review

1차 프로젝트보다 성장한 소통

프론트엔드, 백엔드 2차 프로젝트 때문인지 좋은 팀원들 덕분인지 정말 원활한 소통이 이루어졌다. 애자일 하게 가져온 계획 덕분에 정말 일정에 맞춰 딱 끝낼 수 있었다! 1차 프로젝트 때 아쉬웠던 소통 방식은 2차에서 전부 풀었던 거 같다. 그리고 백엔드 팀원 중에 한 말 중에 기억나는 말이 있었다. frontend 팀원 분들이 이번에는 mock 데이터를 만들게 하지 않을 거라는 말이었다. 이러한 부분에서 프론트엔드팀에 대한 배려 & 시간 낭비가 없었고 좀 더 디테일한 기능 & 생소한 기술을 완성할 수 있었다. 그리고 첫 의사소통을 어떻게 해야 하는지 1차에서 초반에 못 만들었던 프로트와 백엔드의 연결고리를 잘 만들었던 부분은 나 자신에게 칭찬을 해줘야 될 부분인 거 같다.

새로운 git rebase!

이번 팀 프로젝트는 rebase을 사용하였다. 처음으로 rebase를 사용하여 보았는데 이번에는 두려움보다는 실수해도 지금 해보자는 마음이 커서 혼자 rebase merge도 시켜보고 팀원들과 함께 conflict도 잡으며 git에 대해 두려워하기보다는 자신감도 생겼고 git 지식 부분에서도 많이 성장한 것 같다.

시간관리

이번에 시간관리 면에서 주요기능만 계획을 잡았고 디테일 하게 팀원들과 업무분배를 하였다. 자신의 업무가 끝나면 다른 일을 먼저 하는 거 보다는 팀원들의 그날 일을 도와주었고 프로젝트 기간을 앞당겨서 추가기능까지 구현할 수 있었다. 2주,3주는 프로젝트 하기에 정말 짧은 시간이라고 생각한다. 이런 짧은 프로젝트일 수록 철저한 시간관리가 필요한 거 같다.

아쉬운 부분

넷플릭스 같은 경우 시간별로 필요한 바이트 단위 데이터를 랜덤하게 불러온다. 이 부분은 핑계일 수도 있겠지만 주어진 시간이 너무 조금밖에 있지 않아 구현하지 못하였다. 추후 기회가 된다면 영상 스트리밍에 관심이 있는 분들과 좀 더 깊이 있는 대화를 통하여 리팩토링 하고 싶다. (관심이 있는 분들 댓글 남겨 주세요!)

팀 프로젝트 후기

추석 포함 3주라는 짧은 시간 & 생소한 기술에 다들 정신이 없었을 만한 프로젝트였지만 웃음이 끊이질 않았던 프로젝트였다. 백엔드 팀원 분들은 프런트에 대한 배려가 컸고 프론트 팀원분들은 생소한 기술에 포기하고 싶은 순간이 여럿이었던 나에게 응원을 보내주고 "완성을 못 해도 되니깐 할 수 있는데 까지 하자" 라는 말은 나에게 너무 큰 응원이 되었다. 발표가 끝나고는 뿌듯한 마음도 있었지만, 팀원들과 다음 프로젝트가 없다는 것이 너무 아쉬웠다. 이번 프로젝트를 통해 다시 한 번 느낀 것은 개발 역량도 물론 중요하겠지만, 팀원들과의 소통 그리고 시너지라는 것이 정말 중요하다는 것을 느꼈다. 그리고 이번 프로젝트를 통해 나에게 한 마디 해주고 싶은 말은 "고통은 일시적이지만 포기는 평생 갑니다." 라는 말이다. 이번 프로젝트 하면서 잠을 줄여가면서 프로젝트를 하였다. 이 기간 동안 몸은 힘들었지만 내가 포기했다면 후에는 그게 더 힘들었을 거 같다는 생각이 든다.

유튜브 링크

https://www.youtube.com/watch?v=cxjxLiupVy4

14개의 댓글

comment-user-thumbnail
2021년 10월 6일

잘봤습니다!

1개의 답글
comment-user-thumbnail
2021년 10월 7일

git-flow 도 한번 사용해보세요! ㅎㅎ

1개의 답글
comment-user-thumbnail
2021년 10월 13일

dsa

답글 달기
comment-user-thumbnail
2021년 10월 13일
답글 달기
comment-user-thumbnail
2021년 10월 21일

와 정말 대단합니다!!

1개의 답글
comment-user-thumbnail
2021년 11월 3일

진짜 너무 멋지십니다!

1개의 답글
comment-user-thumbnail
2022년 2월 16일

팬이에요 ~!!! 😛 어디서 했니?

1개의 답글
comment-user-thumbnail
2023년 2월 9일

혹시 스트리밍 구현하실 때
비디오 데이터를 dash나 hls같은 방식으로 안쪼개고
mov 데이터 그 자체로 사용하신건가요?

1개의 답글