Timpossible history

[Node JS] Express JS, 서버에서 클라이언트로 동영상 보내기(Feat. Streaming) 본문

백엔드/Node JS

[Node JS] Express JS, 서버에서 클라이언트로 동영상 보내기(Feat. Streaming)

팀파서블 2024. 2. 29. 20:25

웹사이트 구현 중, 대용량의 비디오 파일을 서버에서 전송을 해야하는 기능을 구현해야했다. 클라이언트 사이드에 파일을 저장시켜놓을 수가 없으니 서버에서 데이터 처리를 해야하는데, 이 서버에서 대용량 비디오의 데이터를 보내면서 재생시키도록 하기 위해서는 스트리밍 기능을 구현해야 한다. 이 포스트에서는 이 스트리밍 기능을 Express JS 기반 서버에서 구현해보도록 할 예정이다.

 

1. 프로젝트 시작

mkdir videoStreaming
cd videoStreaming
npm init -y

 

videoStreaming이라는 폴더를 만들어 이동해주고, npm init을 해준다.

그리고 프로젝트에 필요한 패키지를 설치해준다.

npm i express pug

 

express와 html 템플릿 엔진 pug를 설치해준다.

 

2. 소스코드 구현

1) 서버 사이드

// src/server.js

const express = require("express");
const fs = require("fs");

const videoPath = "video.mp4";
const app = express();

app.set("view engine", "pug");
app.set("views", __dirname + "/views");
app.use("/public", express.static(__dirname + "/public"));
app.get("/", (req, res) => res.render("home"));

app.use("/video", (req, res) => {
  const stat = fs.statSync(videoPath);
  const fileSize = stat.size;
  const range = req.headers.range;

  if (range) {
    const parts = range.replace(/bytes=/, "").split("-");
    const start = parseInt(parts[0], 10);
    const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;
    const chunkSize = end - start + 1;
    const file = fs.createReadStream(videoPath, { start, end });
    const headers = {
      "Content-Range": `bytes ${start}-${end}/${fileSize}`,
      "Accept-Ranges": "bytes",
      "Content-Length": chunkSize,
      "Content-Type": "video/mp4",
    };

    res.writeHead(206, headers);
    file.pipe(res);
  } else {
    const headers = {
      "Content-Length": fileSize,
      "Content-Type": "video/mp4",
    };
    res.writeHead(200, headers);
    fs.createReadStream(videoPath).pipe(res);
  }
});

app.listen(8080, () => console.log("connected!"));

 

뷰 엔진은 pug로 세팅해주고, / 경로로 들어왔을 때 home.pug를 렌더링 할 수 있도록 세팅해주었다.

간단하게 클라이언트 사이드의 구현을 위해서 home.pug의 코드를 다음과 같이 짜주었다.

// src/views/home.pug

doctype html
html(lang="en")
    head
        meta(charset="UTF-8")
        meta(name="viewport", content="width=device-width, initial-scale=1.0")
        title Home
        link(rel="stylesheet", href="https://unpkg.com/mvp.css@1.12/mvp.css")
    body 
        h1 This is home!
        button#btn go watch video
        script(src='/public/js/home.js')
        
        

// src/public/js/home.js

const button = document.querySelector("#btn");

button.addEventListener("click", (e) => {
  e.preventDefault();
  window.location = "/video";
});

 

home.pug와 연결한 script 코드는 간단하게 버튼을 눌렀을 때 /video 경로로 이동하도록 해준다.

 

// src/views/video.pug

doctype html
html(lang="en")
    head
        meta(charset="UTF-8")
        meta(name="viewport", content="width=device-width, initial-scale=1.0")
        title video
        link(rel="stylesheet", href="https://unpkg.com/mvp.css@1.12/mvp.css")
    body
        h1 Watch the video
        video(width="640" height="360" controls)
            source(src="http://localhost:8080/video" type="video/mp4")
            p Your browser does not support the video tag.

 

/video의 경로로 이동하였을 때 서버에서는 미리 정해져있는 video 파일의 데이터를 스트리밍하여 클라이언트 사이드로 보내어 재생할 수 있도록 하는 것을 확인할 수 있다.

 

3. 참고

보통 서버에서도 여러 개의 대용량 비디오 파일을 다루기 쉽지 않기 때문에, 클라우드 서비스에 저장을 시켜놓고, 해당 비디오 파일의 경로를 DB에 저장하여 데이터를 불러올 때, 이 비디오가 저장되어 있는 클라우드의 url을 데이터베이스에서 불러와, 이를 스트리밍할 수 있도록 하는 것이 훨씬 효율적이다.