express 비디오 스트리밍 구현

쩡아지🐶·2024년 3월 2일

express 질문저장소

목록 보기
4/5
post-thumbnail

1) 원본 영상을 multi-response로 streaming 하기

https://www.thisdot.co/blog/building-a-multi-response-streaming-api-with-node-js-express-and-react

2) 처음부터 영상을 잘라놓고 서버에서는 잘라진 파일을 연속적으로 서비스

hls-server 서버를 별도로 만들고 싶지 않고 기존에 작업한 api 서버에 영상 서비스 기능도 추가하고 싶었다.
=> m3u8를 video로 재생하는 html을 서비스한다.

2-1) nodejs로 영상 작게 분할하기 (ffmpeg)

yarn add fluent-ffmpeg @ffmpeg-installer/ffmpeg

fluent-ffmpeg으로 영상 썸네일도 만들 수 있는데 나중에 한번 해봐야할듯.

영상을 변환하는 소스를 추가한다. (videos 하위에 있는 8개의 동영상을 가져와서 videos/stream/개별 폴더 에 분할한 파일을 생성)
ffmpeg에 옵션을 줘서 분할하는 파일의 사이즈를 조절한다거나 할 수 있는 것 같다.

const fs = require('fs');
const ffmpegPath = require('@ffmpeg-installer/ffmpeg').path;
const ffmpeg = require('fluent-ffmpeg');
ffmpeg.setFfmpegPath(ffmpegPath);

Array.from({ length: 8 })
  .map((_name, idx) => `video_${idx + 1}`)
  .forEach((fileName) => {
    var command = ffmpeg(`videos/${fileName}.mp4`)
      .input(fs.createReadStream(`videos/${fileName}.mp4`))
      .output(`videos/stream/${fileName}/output.m3u8`)
      .on('end', () => {
        console.log(`${fileName} end`);
      })
      .run();
  });

node로 실행하면 저렇게 조각난 파일과 그 파일들의 연결고리(?)인 m3u8 파일이 생성된다.

node ffmpeg.cjs

2-2) m3u8를 video로 재생하는 html 서비스

<!doctype html>
<html>
  <head>
    <title>{{fileName}}</title>
    <style>
      body { margin: 0; padding: 0; }
      video { display: block; background-color: #141414; margin: 0; padding: 0; width: 100%; aspect-ratio: 16 / 9; }
    </style>
  </head>
  <body>
    <video id="video" controls></video>
    <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
    <script>
      const video = document.getElementById('video');
      const videoSrc = 'http://localhost:8080/videos/stream/{{id}}/output.m3u8';

      if (Hls.isSupported()) {
        const hls = new Hls();
        hls.loadSource(videoSrc);
        hls.attachMedia(video);
        hls.on(Hls.Events.MANIFEST_PARSED, () => {
          video.play();
        });
      } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
        alert('Hls not supported');
        video.src = videoSrc;
        video.addEventListener('loadedmetadata', () => {
          video.play();
        });
      }
    </script>
  </body>
</html>

일단 player.html을 추가한다. title이나 videoSrc는 아이템마다 다르기 때문에 replace하기 위해 변수화했다.

server 파일에 /videos/stream 하위의 정적 파일을 서비스할 수 있게 셋팅하고, /html/stream/~으로 접근 시 replace한 html을 서비스한다.

/**
 * 정적 파일 서비스
 */
app.use('/videos/stream', express.static('videos/stream'));

/**
 * 영상 재생하는 html 제공
 */
app.get('/html/stream/:contId', (
  req: Request<{
    contId: string;
  }>,
  res: Response
) => {
  try {
    const video = vidoesParsed.find(
      (item) => String(item.contId) === req.params.contId
    );
  
    if (video) {
      const playerHTML = fs.readFileSync(path.join(
        __dirname,
        '../videos/player.html'
      ), 'utf8').replace('{{id}}', video.fileName || '').replace('{{fileName}}', video.fileName || '');

      res.status(200).type('html').send(playerHTML);
    } else {
      res.status(200).type('application/json').send(make500Response('영상 없음'));
    }
  } catch (e) {
    res
      .status(200)
      .send(make500Response(e instanceof Error ? e.message : String(e)));
  }
});

2-3) 서버를 실행해서 영상이 재생되는지 확인

잘됨. 굿
iframe src로 사용했을 때도 잘나온다! 끗

profile
React, Next.js, Express STUDY

0개의 댓글