매일 땡기는 마라 코딩

[인프런] 유튜브 사이트 만들기 (1) 본문

클론코딩

[인프런] 유튜브 사이트 만들기 (1)

cmkoi1 2023. 1. 6. 23:00

따라하며 배우는 노드, 리액트 시리즈 - 유튜브 사이트 만들기 강의 

 

[무료] 따라하며 배우는 노드, 리액트 시리즈 - 유튜브 사이트 만들기 - 인프런 | 강의

이 강의를 통해 리액트와 노드의 개념을 익히는 것뿐만이 아닌 실질적으로 어떻게 웹사이트를 만들 수 있는지를 배울 수 있습니다., - 강의 소개 | 인프런...

www.inflearn.com

※ 해당 링크 강의 내용을 바탕으로 작성된 포스팅입니다.

 

 

 

 

전체적인 틀 만들고 Mongo DB 연결

1. Boiler-plate 사용 이유

  • 더 빠르게 개발을 하기 위해.
  • 프로젝트의 중요한 부분에 집중하기 위해.

앱의 기능을 다 만들려고 하면 기본적인 부분을 만드는 것에 시간을 지나치게 할애할 수 있다.

 

 

2. Boiler Plate 다운로드

GIT HUB clone or download https://github.com/jaewonhimnae/boilerplate-mern-stack

 

GitHub - jaewonhimnae/boilerplate-mern-stack: Boilerplate when you use REACT JS, MONG DB, EXPRESS JS, REDUX

Boilerplate when you use REACT JS, MONG DB, EXPRESS JS, REDUX - GitHub - jaewonhimnae/boilerplate-mern-stack: Boilerplate when you use REACT JS, MONG DB, EXPRESS JS, REDUX

github.com

파일 압축을 푼 후, vs code와 같은 텍스트 에디터를 통해 열 수 있다.

 

 

3. Boiler Plate 실행 방법 

Boiler Plate의 코드를 보면 client와 server로 나뉘어져 있으며, client는 리액트, server는 노드로 구성되어 있다. 

  • Boiler Plate 실행을 위해서는 client와 server의 Dependencies(라이브러리)를 다운받아야 한다.
    1. 터미널 실행.
    2. npm 명령어를 사용하려면 노드가 다운되어 있어야 한다. node -v로 노드 버전 확인. 다운되어 있지 않다면 Node.js 다운로드 후 버전 확인. → v16.14.2
    3. npm install 명령어 입력하여 자동적으로 디펜던시 다운. 오류가 날 경우 npm install -g로 전역설치한다.
      • 'C:\Users\이름\Desktop\A\study\cloneCoding\youtube\boilerplate-mern-stack-master' root 디렉토리이며, npm install 시 server에 해당되는 디펜던시가 다운된다.
      • cd client 명령어를 통해 'C:\Users\이름\Desktop\A\study\cloneCoding\youtube\boilerplate-mern-stack-master\client'로 이동 후 npm install하여 client에 해당되는 디펜던시를 다운로드한다. 돌아올 때는 cd ..
  • dev.js 파일 생성
    1. boilerplate-mern-stack-master  server → config 진입.
    2. 해당 위치에 dev.js 파일 생성.
    3. prod.js의 코드를 카피해서 dev.js에 붙여넣기 후, process.env.MONGO_URI 부분을 지우고 ''(작은 따옴표)로 대체 및 저장.
      • 개발을 로컬에서 할 수도 있고, deploy해서 프로덕션 모드로 할 수도 있다. 로컬은 dev.js에 있는 변수, 프로덕션 모드는 prod.js에 있는 변수를 사용한다. 또한, key.js는 둘 중 어디에서 작업을 하고 있는지 if else문으로 인식해 준다.

deploy란?

배치하다 라는 뜻으로, 파일들을 적절한 곳에 배치하는 것을 말한다. 이때, 배치한 곳의 파악을 위해 위치를 데이터베이스화 혹은 정형화해 준다. 해당 역할을 하는 파일을 배치 디스크립터라고 한다.


  • MongoDB 로그인
    1. 사이트 회원가입 https://www.mongodb.com
    2. Create 눌러 Create New Cluster 화면에서 Shared 선택. AWS 서버와 국가 선택 후, Cluster Tier M0 Sandbox 선택. Cluster Name 변경해 주고 Create Cluster 버튼 눌러 Cluster 생성해 준다.
    3. Cluster 생성 후 Database에서 해당 Cluster의 Connect버튼 눌러 Connect your application 선택, Node.js와 3.0 or later 선택. 주소를 복사해 주고 close 누른다.
    4. 복사한 주소를 dev.js 코드의 작은 따옴표 안에 붙여넣기해 준다.
    5. Database Access에서 Add New Database User 버튼 눌러 UserName과 Password 입력 후 Add User 버튼 눌러 유저 생성.
    6. 생성된 유저의 UserName과 Password를 4에서 붙여넣은 주소의 <username>과 <password>부분에 각각 적어 주고 저장.
 

MongoDB: The Developer Data Platform

Get your ideas to market faster with a developer data platform built on the leading modern database. MongoDB makes working with data easy.

www.mongodb.com

 

 

 

Boiler Plate 강의

  • Boiler Plate: 어떤 웹 사이트를 만들 때 로그인과 같이 자주 쓰이는 기능을 만들어, 어디든 가져다 쓸 수 있게 만든 것.

Boiler Plate 강의 https://youtu.be/fgoMqmNKE18

 

 

 

비디오 업로드 FORM 만들기 (1)

1. Upload Page 만들기

  • npm run dev 명령어로 프로그램 실행해 보기. (여기서 오류로 3시간 헤맴.)
    • package.json 파일의 scripts를 보면 start는 server 부분 가동, backend는 nodemon 라이브러리를 이용해 server 부분 가동, frontend는 client 부분 가동이 된다. dev는 concurently 라이브러리를 이용하여, 명령어 하나로 server, client 순으로 한번에 가동한다.
    • nodemon: 코드 수정 및 저장 작업 때마다 서버를 죽이고 다시 켜지 않아도, 자동적으로 변환된 코드를 인식해 보여 주는 라이브러리.

처음 에러 발생

https://figureking.tistory.com/512

 

'concurrently'은(는) 내부 또는 외부 명령, 실행할 수 있는 프로그램, 또는 배치 파일이 아닙니다.

Node.js yarn 에러. yarn dev 명령어 실행시 yarn run v1.22.4 warning package.json: No license field $ concurrently --kill-others-on-fail "yarn sever" "yarn client" 'concurrently'은(는) 내부 또는 외부 명령, 실행할 수 있는 프로그램,

figureking.tistory.com

npm i concurrently express --save 명령어로 package.json 버전 상향. 해결 안 됨.

package.json 버전 상향 후 에러

파일 전부 삭제하고 다시 설치.

다시 설치 후 npm install 에러

 package.json에서 bcrypt 버전 "^5.0.0"으로 상향 후 npm install. npm dev run으로 실행됨!

실행 화면이 자동으로 켜지지 않아 헤매는 과정...
실행은 됐으나, MongoDB Connected 부분에서 무한 로딩

root와 client 다시 npm install 후 해결. 자동으로 localhost:3000 페이지 켜짐.

 

  • 로그인 및 회원가입
    • bolier plate에 Log In, Sign up 기능 이미 구현되어 있음.

처음 랜딩 페이지 야호

회원가입이 안 돼서 코드를 봤는데...

Sign up 중 Submit 버튼이 눌리지 않아 봤더니 나온 오류...

프록시 문제라길래 백 먼저 실행하고 프론트 실행했는데 실패. DB 문제인가 해서 Cluster 삭제하고 새로 만들었더니 해결.

 

  • Redux DevTool 사용하면 로그인 시 리덕스에 유저 정보 저장. redux extension 검색하여 다운로드.

Redux DevTool https://chrome.google.com/webstore/detail/reduxdevtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=ko

 

Redux DevTools

Redux DevTools for debugging application's state changes.

chrome.google.com

  • Upload Page 만들기
    1. boilerplate-mern-stack-master/client/src/components/views 들어가면 페이지별로 폴더가 나뉘어져 있다.
    2. views 폴더에 VideoUploadPage 폴더 생성.
    3. VideoUploadPage 폴더에 VideoUploadPage.js 파일 생성.

VideoUploadPage.js 

import React from "react"; //익스텐션 다운 시 rfce 단축키로 바로 생성 가능

function VidoeoUploadPage() { //펑셔널 컴포넌트 생성
    return (
        <div>
            VidoeoUploadPage
        </div>
    )
}

export default VidoeoUploadPage //다른 파일에서 컴포넌트를 이용할 수 있게 export
  • 컴포넌트는 클래스, 펑서녈이 있다. 클래스 컴포넌트는 속도는 좀 느리지만 많은 기능을 구현하는 데 편하기 때문에 React Hook이 나오기 전에는 클래스를 더 많이 썼다. Hook이 나오고 펑셔널 컴포넌트도 클래스에서 할 수 있는 거의 모든 것이 가능하게 만들어, 퍼포먼스가 좋은 펑셔널 컴포넌트를 쓰는 추세이다.
  • Extensions 추천: ESLint, Material Theme, ES7 React/Redux/GraphQL/React-Native snippets

 

 

2. Upload Page Route 만들기

boilerplate-mern-stack-master/client/ src/components/App.js 

//import Footer from "./views/Footer/Footer 
import VideoUploadPage from "./views/VideoUploadPage/VideoUploadPage"
//<Route exact path="/register" component={Auth(RegisterPage, false)} />
<Route exact path="/video/upload" component={Auth(VideoUploadPage, true)} />
//null 아무나 진입 가능, false 로그인 한 사람 진입 불가능, true 로그인 한 사람만 진입 가능. Client/src/hoc/auth.js에서 코드 확인 가능.

앱 실행하고 로그인 후, localhost:3000/video/upload 페이지 확인.

 

 

3. Upload Page Header Tab 만들기

boilerplate-mern-stack-master\client\src\components\views\NavBar\Sections\RightMenu.js 코드 수정.

  if (user.userData && !user.userData.isAuth) { //로그인 안 했을 경우
    return (
      <Menu mode={props.mode}>
        <Menu.Item key="mail">
          <a href="/login">Signin</a>
        </Menu.Item>
        <Menu.Item key="app">
          <a href="/register">Signup</a>
        </Menu.Item>
      </Menu>
    )
  } else { //로그인 했을 경우
    return (
      <Menu mode={props.mode}>
        <Menu.Item key="upload">
          <a href="/video/upload">Video</a> //수정 부분
        </Menu.Item>
        <Menu.Item key="logout">
          <a onClick={logoutHandler}>Logout</a>
        </Menu.Item>
      </Menu>
    )
  }

 

 

4. Form Template 만들기

css 프레임워크 ANT DESIGN 사용.

VideoUploadPage.js

import React from "react"; //익스텐션 다운 시 rfce 단축키로 바로 생성 가능
import { Typography, Button, Form, message, Input, Icon } from 'antd'; //ANT DESIGN

const { TextArea } = Input;
const { Title } = Typography;

function VidoeoUploadPage() { //펑셔널 컴포넌트 생성
    return (
        <div style={{maxWidth:'700px', margin:'2rem auto' }}>
            <div style={{textAlign:'center', marginBottom:'2rem' }}>
                <Title level={2}>Upload Video</Title>
            </div>

            <Form onSubmit>
                <div style={{ display:'flex', justifyContent:'space-between' }}>
                    {/* Drop zone */}

                    {/* Thumbnail 썸네일 부분 */}
                    <div>
                        <img src alt />
                    </div>
                </div>

                <br />
                <br />
                <label>Title</label>
                <Input
                    onChange
                    Value
                />
                <br />
                <br />
                <label>Description</label>
                <TextArea
                    onChange
                    value
                />
                <br />
                <br />

                <select onChange>
                    <option key value></option>
                </select>

                <br />
                <br />
                <select onChange>
                    <option key value></option>
                </select>
                <br />
                <br />
                <Button type="primary" size="large" onClick>
                    Submit
                </Button>

            </Form>

        </div>
    )
}

export default VidoeoUploadPage //다른 파일에서 컴포넌트를 이용할 수 있게 export

 

 

5. Drop-zone 라이브러리 다운받기

  1. Ctrl+C로 서버를 내리고, clear 명령어로 터미널 내역 지우기.
  2. Drop-zone은 클라이언트를 위한 것이기 때문에, cd client 명령어로 위치 변경.
  3. npm install react-dropzone --save 명령어로 다운로드.
    • --save 명령어를 사용해야 package.json에 표시된다.
  4. cd .. 명령어를 사용해 위치 변경 후, npm run dev 명령어로 실행.

VideoUploadPage.js

//import { Typography, Button, Form, message, Input, Icon } from 'antd'; //ANT DESIGN
import Dropzone from 'react-dropzone';
                    {/* Drop zone */}

                    <Dropzone
                    onDrop
                    multiple
                    maxSize>
                    {({ getRootProps, getInputProps }) => (
                        <div style={{width: '300px', height: '240px', border: '1px solid lightgray', display:'flex',
                        alignItems:'center', justifyContent:'center'}} {...getRootProps()}>
                            <input {...getInputProps()} />
                            <Icon type="plus" style={{fontSize:'3rem'}} />
                        </div>
                    )}

                    </Dropzone>

 

 

 

비디오 업로드 FORM 만들기 (2)

6. onChange func 만들기

  • State는 value들을 저장해 놓았다가, 서버에 한꺼번에 보낸다.
  • 또한, onChange 부분을 수정해 줘야 state가 변동되고 value가 바뀌어 타이핑이 출력된다.
  • select 부분은 코드를 두 번 적을 수도 있고 map 메소드를 사용할 수도 있는데, map 메소드를 사용하였다.

VideoUploadPage.js

import React, { useState } from "react"; //수정해 줘야 함
import { Typography, Button, Form, message, Input, Icon } from 'antd'; //ANT DESIGN
import Dropzone from 'react-dropzone';
import Item from "antd/lib/list/Item";

const { TextArea } = Input;
const { Title } = Typography;

const PrivateOptions = [ //map 메소드에 필요함. select 부분.
    { value: 0, label: "Private" },
    { value: 1, label: "Public" }
]

const CategoryOptions = [
    { value: 0, label: "Film & Animation" },
    { value: 1, label: "Autos & Vehicles" },
    { value: 2, label: "Music" },
    { value: 3, label: "Pets & Animals" }
]

function VidoeoUploadPage() { //펑셔널 컴포넌트 생성
    
    const [VideoTitle, setVideoTitle] = useState("") //처음 state은 빈 스트림으로, 이름은 겹치지 않게 VideoTitle
    const [Description, setDescription] = useState("")
    const [Private, setPrivate] = useState(0) //Private=0, Public=1
    const [Category, setCategory] = useState("Film & Animation")
    
    const onTitleChange = (e) => {
        console.log(e.currentTarget) //e가 뭔지 콘솔 창에서 확인
        setVideoTitle(e.currentTarget.value) //e는 이벤트를 뜻함
    }

    const onDescriptionChange = (e) => {
        setDescription(e.currentTarget.value)
    }

    const onPrivateChange = (e) => {
        setPrivate(e.currentTarget.value)
    }

    const onCategoryChange = (e) => {
        setCategory(e.currentTarget.value)
    }
                <Input
                    onChange={onTitleChange}
                    Value={VideoTitle}
                />
                <br />
                <br />
                <label>Description</label>
                <TextArea
                    onChange={onDescriptionChange}
                    value={Description}
                />
                <br />
                <br />
                <select onChange={onPrivateChange}>
                    {PrivateOptions.map((item, index) => (
                        <option key={index} value={item.value}>{item.label}</option> //key 값이 있어야 에러나지 않음
                    ))}                
                </select>
                <br />
                <br />
                <select onChange={onCategoryChange}>
                    {CategoryOptions.map((item, index) => (
                        <option key={index} value={item.value}>{item.label}</option>
                    ))}
                </select>

 

 

7. onDrop func 만들기

  • onDrop 수정하여 드랍 시 트리거가 일어나게 한다.

VideoUploadPage.js

    const onDrop = (files) => {
        //files 파라미터에 파일의 정보가 담겨 있음, 파라미터로 파일을 받음
        let formData = new FormData
        const config = {
            header: {'content-type': 'multpart/form-data'}
        }
        formData.append("file", files[0]) //파일을 보내줌
        //위의 코드가 없으면 서버에 파일을 보낼 때 오류가 생김
        Axios.post('/api/video/uploadfiles', formData, config) //Axios: 리퀘스트를 서버에 보내고 받을 때 쓰는 라이브러리
            .then(response => { //서버 처리에 대한 response를 가져옴
                if(response.data.success) {//성공
                    console.log(response.data) //서버에서 파일 경로 url, 이름을 받음
            
                } else{ //실패
                    alert('비디오 업로드를 실패했습니다.')
                }
            })
    }

    //return (
                    <Dropzone
                    onDrop={onDrop}
                    multiple={false} //파일 여러 개를 올리면 true
                    maxSize={1000000000} //사이즈 조정
                    >

 

 

 

Multer로 노드 서버에 비디오 저장하기

1. 노드 서버에 파일을 저장하기 위한 Dependency 다운로드

루트에 npm install multer --save 명령어로 multer 다운로드.

→ 다시 서버 가동하면 Video 모델을 만들지 않았기 때문에 오류가 난다.

 

video.js

//const { auth } = require("../middleware/auth");
const multer = require("multer");

 

 

2. 비디오 파일 서버로 보내기

  • boilerplate-mern-stack-master\server\routes에 video.js 파일 생성 후, users.js 파일 코드 복사 및 수정. 

video.js

const express = require('express');
const router = express.Router();
const { Video } = require("../models/Video");

const { auth } = require("../middleware/auth");

//=================================
//             Video
//=================================

router.post('/uploadfiles', (req, res) => { //index에서 받아옴. req를 통해 파일을 받음

    //비디오를 서버에 저장

})

module.exports = router;
  • videoUploadPage에서 보내면 index를 통해 video가 받아오기 때문에 index.js 코드 수정.

index.js

//app.use('/api/users', require('./routes/users'));
app.use('/api/video', require('./routes/video'));

 

 

3. 받은 비디오 파일 서버에서 저장

  • boilerplate-mern-stack-master 경로에 동영상 파일을 저장할 uploads 폴더 만들어 준다.

video.js

//const multer = require("multer");


//STORAGE MULTER CONFIG
let storage = multer.diskStorage({
    destination: (req, file, cb) => { //파일을 올리면 어디다 저장할지 설명
        cb(null, "uploads/"); //boilerplate-mern-stack-master/uploads 폴더에 저장
    },
    filename: (req, file, cb) => { //어떤 파일 이름으로 저장할 것인지
        cb(null, `${Date.now()}_${file.originalname}`);
    }, //ex) 202301110409_hello
    fileFilter: (req, file, cb) => {
        const ext = path.extname(file.originalname)
        if (ext !== '.mp4') { //파일 형식. 비디오만
            return cb(res.status(400).end('only mp4 is allowed'), false);
        }
        cb(null, true)
    }
});

var upload = multer({ storage: storage }).single("file") //파일을 하나만 받음

 

 

4. 파일 저장 경로를 클라이언트로 전달

video.js

router.post('/uploadfiles', (req, res) => { //index에서 경로를 받아옴. req를 통해 파일을 받음

    //비디오를 서버에 저장
    uploads(req, res, err => {
        if(err) {  //VideoUploadPage.js의 onDrop 부분이 처리
            return res.json({ success: false, err })
        }
        return res.json({ success: true, url: res.req.file.path, fileName: res.req.file.filename })
    }) //url은 uploads 폴더의 경로를 보내 준다.

})

 

 

 

ffmpeg로 비디오 썸네일 생성하기

1. 썸네일 생성을 위한 Dependency 다운로드

ffmpeg 설치 https://angelplayer.tistory.com/351

 

[FFmpeg] FFmpeg 설치 방법 (Windows OS)

Windows 환경에서 FFmpeg 설치방법을 알아보도록 하겠습니다. 먼저 FFmpeg 사이트로 이동합니다. https://ffmpeg.org/ FFmpeg Converting video and audio has never been so easy. $ ffmpeg -i input.mp4 output.avi News July 22nd, 2022, FF

angelplayer.tistory.com

루트에서 npm install fluent-ffmpeg --save 명령어를 통해 fluent-ffmpeg 라이브러리 다운로드.

 

 

2. 서버에 저장된 비디오를 이용해 썸네일 생성

VideoUploadPage.js

                //if(response.data.success) {//성공
                    //console.log(response.data) //서버에서 파일 경로 url, 이름을 받음

                    let variable = {
                        url:response.data.url,
                        fileName: response.date.fileName
                    }

                    Axios.post('/api/video/thumbnail', variable) //위의 variavble 값 넣어서 서버에 보내 줌
                    .then(response => {
                        if(response.data.success) {

                        } else {
                            alert('썸네일 생성에 실패했습니다.')
                        }
                    })

 

 

3. 생성된 썸네일을 서버에 저장

  • boilerplate-mern-stack-master\uploads 경로에 썸네일 파일을 저장할 thumbnails 폴더 만들어 준다.

video.js

//const multer = require("multer");
var ffmpeg = require("fluent-ffmpeg");
router.post('/thumbnail', (req, res) => {

    //썸네일 생성하고 비디오 러닝타임도 가져오기

    let filePath = ""
    let fileDuration = ""

    //비디오 정보 가져오기
    ffmpeg.ffprobe(req.body.url, function (err, metadata) { //uploads 폴더에서 비디오 가져옴
        console.dir(metadata); //all meatadata
        console.log(metadata.format.duration);
        fileDuration = metadata.format.duration //비디오 러닝타임
    });

    //썸네일 생성
    ffmpeg(req.body.url) //클라이언트에서 온 비디오 저장 경로 (uploads/)
    .on('filenames', function (filenames){ //비디오 썸네일 파일 이름 생성
        console.log('Will generate ' + filenames.join(', '))
        console.log(filenames)

        filePath = "uploads/thumbnails/" + filenames[0]
    })
    .on('end', function () { //썸네일 생성 후의 행동
        console.log('Screenshots taken');
        return res.json({ success: true, url: filePath, fileDuration: fileDuration});
    }) //fileDuration는 러닝타임
    .on('error', function (err) { //에러 대처 방법
        console.error(err);
        return res.json({ success: false, err});
    })
    .screenshots({ //스크린샷 옵션
        //will take screenshots at 20%, 40%, 60%, and 80% or the video
        count: 3, //세 개의 썸네일을 찍을 수 있음
        folder: 'uploads/thumbnails', //썸네일 저장 폴더
        size: '320x240', //썸네일 사이즈
        //'%b': 원래 파일 이름 (filename w/o extension, 익스텐션은 빼고)
        filename: 'thumbnail-%b.png' 
    })


})

//module.exports = router;

앱 실행해 보려는데 오류 발생...

자동으로 생성된 import { response } from "express"; 지우고 해결. 앱 실행 후 동영상을 드롭존에 업로드하려고 했더니 새로운 오류 발생.

import Axios from "axios";

VideoUploadPage.js의 import { Axios } from "axios"; 부분을 위 코드처럼 바꿔 줘야 한다고.

참고 https://velog.io/@vlsxm/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-Axios-%EC%98%A4%EB%A5%98-Uncaught-TypeError-Axios.post-is-not-a-function

 

리액트 라이브러리 Axios 오류 "Uncaught TypeError: Axios.post is not a function"

axios_WEBPACK_IMPORTED_MODULE_10\_\_.Axios.post is not a functionAxios 라이브러리를 리액트를 사용하는중 이런 오류를 몇번씩 봤었지만 힘들게 오류를 해결하고 나중에 가서 또 잊어버리고 무한지옥에서 빠져

velog.io

해결~~

 

 

4. 썸네일 이미지 파일 경로 정보를 클라이언트에 보내기

video.js의 해당 부분이 파일 경로 정보를 클라이언트에게 보내는 부분. 러닝 타임도 같이 보내 준다.

video.js

    .on('end', function () { //썸네일 생성 후의 행동
        console.log('Screenshots taken');
        return res.json({ success: true, url: filePath, fileDuration: fileDuration});

 

videoUploadPage.js가 정보를 받아 state에 저장해 주기 위해 state 생성.

VideoUploadPage.js

    //const [Category, setCategory] = useState("Film & Animation")
    const [FilePath, setFilePath] = useState("")
    const [Duration, setDuration] = useState("")
    const [ThumbnailPath, setThumbnailPath] = useState("")
                    //썸네일 및 비디오 파일 주소, 비디오 러닝타임과 같은 정보를 state에 저장해 준다.
                    setFilePath(response.data.url)

                    Axios.post('/api/video/thumbnail', variable) //위의 variavble 값 넣어서 서버에 보내 줌
                    .then(response => {
                        if(response.data.success) {
                            console.log(response.data)
                            setDuration(response.data.fileDuration)
                            setThumbnailPath(response.data.url)

 

 

5. 썸네일 이미지를 화면에 표시

videoUploadPage.js

                    {/* Thumbnail 썸네일 부분 */}

                    {ThumbnailPath && //ThumbnailPath가 있을 때만 렌더링되도록
                    <div>
                        <img src={`http://localhost:5000/${ThumbnailPath}`} alt="thumbnail" />
                    </div>
                    }

 

 

 

비디오 업로드 하기

RDBMS와 MongoDB의 용어 대조

1. 비디오 Collection 만들기

models 폴더의 Video.js 파일에 들어가야 할 값

  • boilerplate-mern-stack-master\server\models 경로에 Video.js 파일 만들고 user.js 내용 복사 붙여넣기 및 수정.
  • user.js는 User.js로 이름 바꿔 준다.

Video.js

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const videoSchema = mongoose.Schema({
    //필드 정의
    writer: {
        type: Schema.Types.ObjectId, //작성자의 Id를 넣으면
        ref: 'User' //User.js에서 정보를 다 불러올 수 있음
    },
    title: {
        type: String,
        maxlength: 50
    },
    description: {
        type: String
    },
    privacy: {
        type: Number //값이 0 아니면 1이니까
    },
    filePath: {
        type: String
    },
    category: {
        type: String
    },
    views: {
        type: Number,
        default: 0 //view 수가 0부터 시작하기 때문
    },
    duration: {
        type: String
    },
    thumbnail: {
        type: String
    }


}, { timestamps: true }) //만든날과 업데이트날 표시


const Video = mongoose.model('Video', videoSchema);

module.exports = { Video }

 

 

2. onSubmit Function 만들기

VideoUploadPage.js

    const onSubmit = (e) => {
        e.preventDefault(); //원래 기능을 방지

    }
    
    //return (
            <Form onSubmit={onSubmit}>
                <Button type="primary" size="large" onClick={onSubmit}>

 

 

3. 요청할 데이터들을 서버로 보내기

  • 위에서 다운받은 Redux를 통해 user 값을 받아온다.

VideoUploadPage.js

function VidoeoUploadPage(props) { //펑셔널 컴포넌트 생성
    
    const user = useSelector(state => state.user); //Redux의 state에 가서 user를 가져와 저장
    const onSubmit = (e) => {
        e.preventDefault(); //원래 기능을 방지
        
        const variables = { //Redux를 통해 값을 가져옴
            writer: user.userData._id,
            title: VideoTitle,
            description: Description,
            privacy: Private,
            filePath: FilePath,
            category: Category,
            duration: Duration,
            thumbnail: ThumbnailPath,
        }

        Axios.post('/api/video/uploadVideo', variables)
            .then(response => {
                if(response.data.success) {
                    console.log(response.data)
                    message.success('성공적으로 업로드를 했습니다.')

                    setTimeout(() => {
                        props.history.push('/')
                    }, 3000); //3초 뒤 랜딩 페이지로 이동

                } else {
                    alert('비디오 업로드에 실패 했습니다.')
                }
            })
    }

Redux

 

 

4. 보낸 데이터들 MongoDB에 저장

  • const { Video } = require("../models/Video.js");  → video.js에 있는 코드로, 비디오 모델을 import 해온다는 의미.

video.js

router.post('/uploadVideo', (req, res) => {

    //비디오 정보들을 저장한다.

    const video = new Video(req.body) //req.body는 모든 값이 담긴 것

    video.save((err, doc) => { //mongoDB 메소드로 저장
        if(err) return res.json({ success: false, err }) //실패
        res.status(200).json({ success: true }) //성공
    })

})


//router.post('/thumbnail', (req, res) => {

 

비디오 업로드 페이지 비디오 관련 데이터
MongoDB에 저장된 비디오 관련 데이터

 

 

 

랜딩 페이지에 비디오들 나타나게 하기

1. 빈 랜딩 페이지 생성

boilerplate-mern-stack-master\client\src\components\views\LandingPage\LandingPage.js

import React from 'react'
import { FaCode } from "react-icons/fa";
import { Card, Icon, Avatar, Col, Typography, Row } from 'antd';
const { Title } = Typography
const { Meta } = Card;

function LandingPage() {
    return (
        <div>

        </div>
    )
}

export default LandingPage

 

 

2. 비디오 카드 Template 만들기

  • Row 1개에 4개의 Column(비디오)가 나오게 하기 위해 사이즈 조정. 24가 화면의 전체 사이즈이며, 화면이 가장 작을 때는 24로 하나의 비디오가 채우고, 중간 정도일 땐 8로 3개의 비디오, 전체 화면일 땐 6으로 하여 4개의 비디오가 보이게 한다. 

LandingPage.js

    return (
        <div style={{ width: '85%', margin: '3rem auto' }}>
            <Title level={2}> Recommended </Title>
            <hr />
            <Row gutter={[32, 16]}> 


                {/* {renderCards}
                <Col lg={6} md={8} xs={24}>
                    <a href={`/video/post/${video._id}`} >
                        <div style={{ position: 'relative'}}>
                            <img style={{ width: '100%' }} src={`http://localhost:5000/${video.thumbnail}`} alt="thumbnail" />
                            <div className='duration'>
                                <span>{minutes} : {seconds}</span>
                            </div>
                        </div>
                    </a>
                    <br />
                    <Meta
                        //avatar={
                        //    <Avatar src={video.writer.image} />
                        //}
                        //title={video.title}
                        description=""
                    />
                    <span>{video.writer.name}</span><br />
                    <span style={{ marginLeft: '3rem' }}>{video.views} views</span> - <span>{moment(video.createdAt).format("MMM Do YY")}</span>
                </Col> */}

            </Row>
        </div>
    )

 

 

3. 몽고 DB에서 모든 비디오 데이터 가져오기

LandingPage.js

import React, { useEffect } from 'react'
import Axios from 'axios';
//function LandingPage() {

    useEffect(() => { //페이지가 로드되면 실행, input이 없으면 계속 실행

        Axios.get('/api/video/getVideos')
        .then(response => {
            if(response.data.success) {
                console.log(response.data)
            } else {
                alert('비디오 가져오기를 실패했습니다.')
            }
        })

    }, [])//[]가 있으면 한 번만 실행

video.js

router.get('/getVideos', (req, res) => {

    //비디오를 DB에서 가져와서 클라이언트에 보낸다.
    
    Video.find() //비디오 컬렉션에 있는 모든 비디오 가져오기
        .populate('writer') //poulate을 해 줘야 writer의 모든 정보 가져오기 가능 안 하면 id만 가능
        .exec((err, videos) => {
            if(err) return res.status(400).send(err);
            res.status(200).json({ success: true, videos})
        })

})


//router.post('/thumbnail', (req, res) => {

가져온 비디오 데이터 확인

 

 

4. 가져온 비디오 데이터들을 스크린에 출력

LandingPage.js

import React, { useEffect, useState } from 'react'
import moment from 'moment';
    const renderCards = Video.map((video, index) => {

        var minutes = Math.floor(video.duration / 68);
        var seconds = Math.floor((video.duration - minutes * 60));

        return <Col lg={6} md={8} xs={24}>
            <div style={{ position: 'relative'}}>
                <a href={`/video/${video._id}`} >
                    <img style={{ width: '100%' }} src={`http://localhost:5000/${video.thumbnail}`} alt="thumbnail" />
                    <div className='duration'>
                        <span>{minutes} : {seconds}</span>
                    </div>
                </a>
            </div><br />
            <Meta
                avatar={
                    <Avatar src={video.writer.image} />
                }
                title={video.title}
            />
            <span>{video.writer.name}</span><br />
            <span style={{ marginLeft: '3rem' }}>{video.views} views</span> - <span>{moment(video.createdAt).format("MMM Do YY")}</span>
        </Col>
    })

    return (
        <div style={{ width: '85%', margin: '3rem auto' }}>
            <Title level={2}> Recommended </Title>
            <hr />
            <Row gutter={[32, 16]}> 


                {renderCards}
                

            </Row>
        </div>
    )
}

export default LandingPage
  • <div style={{ position: 'relative'}}></div>에 감싸져 있는 부분은 비디오 썸네일 관련이며, <span>{minutes} : {seconds}</span>는 minutes와 seconds 변수의 계산 결과를 보여 준다. Meta 부분은 사용자 프로필 관련이다.

boilerplate-mern-stack-master\client\src\index.css 가장 아래에 코드 추가.

.duration{
  bottom: 0;
  right: 0;
  position: absolute;
  margin: 4px;
  color: #fff;
  background-color: rgba(17, 17, 17, 0.0);
  opacity: 0.8;
  padding: 2px 4px;
  border-radius: 2px;
  letter-spacing: 0.5px;
  font-size: 12px;
  font-weight: 500;
  line-height: 12px;
}

랜딩 페이지

 

 

 

비디오 디테일 페이지 만들기

1. 비어있는 디테일 페이지 생성

  • boilerplate-mern-stack-master\client\src\components\views 경로에 VideoDetailPage 폴더 생성.
  • boilerplate-mern-stack-master\client\src\components\views\VideoDetailPage 경로에 VideoDetailPage.js 파일 생성 후 코드 입력.

VideoDetailPage.js

import React from 'react'

function VideoDetailPage() {
  return (
    <div>
        VideoDetailPage
    </div>
  )
}

export default VideoDetailPage

 

 

2. 비디오 디테일 페이지를 위한 Route 만들기

boilerplate-mern-stack-master\client\src\components\App.js

//import VideoUploadPage from "./views/VideoUploadPage/VideoUploadPage"
import VideoDetailPage from "./views/VideoDetailPage/VideoDetailPage"
          //<Route exact path="/video/upload" component={Auth(VideoUploadPage, true)} />
          <Route exact path="/video/:videoId" component={Auth(VideoDetailPage, null)} />

 

 

3. 비디오 디테일 페이지 Template 만들기

  • 화면이 줄어들면 사이드 부분이 아래로 이동하도록.

VideoDetailPage.js

import React from 'react'
import { Row, Col, List, Avatar } from 'antd';

function VideoDetailPage() {



    return (
        <Row gutter={[16, 16]}>
            <Col lg={18} xs={24}>

                <div style={{ width: '100%', padding: '3rem 4rem'}}>
                    <video style={{ width: '100%' }} src controls />

                    <List.Item
                        actions
                    >
                        <List.Item.Meta
                            avatar
                            title
                            description
                        />

                    </List.Item>

                    {/* Comments */}

                </div>

            </Col>
            <Col lg={6} xs={24}>
                Side Videos
            </Col>

        </Row>
    )
}

export default VideoDetailPage
  • LandingPage.js의 <a href={`/video/${video._id}`} > 코드는 랜딩 페이지에 디테일 페이지 링크를 주는 역할.

 

 

4. MongoDB에서 비디오 데이터 가져오기

마지막 부분이 videoId

VideoDetailPage.js

import React, { useEffect, useState } from 'react'
import { Row, Col, List, Avatar } from 'antd';
import Axios from "axios";

function VideoDetailPage(props) {

    const videoId = props.match.param.videoId //App.js 코드 때문에 가능
    const variable = { videoId: videoId}

    const [VideoDetail, setVideoDetail] = useState([])

    useEffect(() => {
        
        Axios.post('/api/video/getVideoDetail', variable) //비디오 id를 같이 보내주기
            .then(response => {
                if(response.data.success) {
                    setVideoDetail(response.data.videoDetail)
                } else {
                    alert('비디오 정보를 가져오길 실패했습니다.')
                }
            })

    }, [])

video.js

router.post('/getVideoDetail', (req, res) => {

    //비디오 id에 맞는 정보를 DB에서 가져와서 클라이언트에 보낸다.
    
    Video.findOne({ "_id" : req.body.videoId })
        .populate('writer')
        .exec((err, videoDetail) => {
            if(err) return res.status(400).send(err)
            return res.status(200).json({ success: true, videoDetail })
        })
    

});

 

 

5. 가져온 데이터들을 스크린에 출력

VideoDetailPage.js

    if(VideoDetail.writer) { //이미지가 로딩 전에 렌더링되어 나는 오류를 방지
        return (
            <Row gutter={[16, 16]}>
                <Col lg={18} xs={24}>
    
                    <div style={{ width: '100%', padding: '3rem 4rem'}}>
                        <video style={{ width: '100%' }} src={`http://localhost:5000/${VideoDetail.filePath}`} controls />
    
                        <List.Item
                            actions
                        >
                            <List.Item.Meta
                                avatar={<Avatar src={VideoDetail.writer.image} />}
                                title={VideoDetail.writer.name}
                                description={VideoDetail.description}
                            />
    
                        </List.Item>
    
                        {/* Comments */}
    
                    </div>
    
                </Col>
                <Col lg={6} xs={24}>
                    Side Videos
                </Col>
    
            </Row>
        )
    } else {
        return (
            <div>...loading</div>
        )
    }

 

로딩
비디오 디테일 페이지

728x90