Node.js + Express: How to stream a video to client side

Updated: December 30, 2023 By: Guest Contributor Post a comment

Introduction

Streaming media, especially audio and video content, has become a fundamental feature of modern web applications. In this guide, we’ll delve into the mechanics of implementing a video streaming service using Node.js and Express. By using modern JavaScript syntax such as arrow functions, async/await, and ES modules, you can create an elegant and efficient streaming solution that enhances the user experience on your application.

Setting Up the Basics

To start with, let’s set up a simple server using Node.js and Express. You’ll need Node.js installed on your machine. Here’s how you can initiate your project:

mkdir video-streaming-app

cd video-streaming-app

npm init -y

npm install express

Now, create an app.js or server.js file for your express server code:

import express from 'express';

const app = express();

const PORT = process.env.PORT || 3000;

app.listen(PORT, () => console.log(`Listening on port ${PORT}`));

Simple Video Streaming

For serving the actual video file, you’ll store it in a public directory.

app.use('/public', express.static('public'));

To establish the frontend, let’s use a basic HTML file:

<video src="/public/myvideo.mp4" controls></video>

This setup allows users to download the whole video file at once, which isn’t true streaming, but it’s a start.

Implementing Streaming

To offer real streaming capabilities, we need to serve the video content in chunks, responding to the client’s range requests.

app.get('/video', (req, res) => {
  const range = req.headers.range;
  if (!range) {
    res.status(400).send('Requires Range header');
    return;
  }
  
  // Extract the start and end positions from the Range header
  
  // Return the specified part of the video file
});

We now need to determine the positions and the size of the requested chunk, using the video file size.

import fs from 'fs';
import { promisify } from 'util';

const stat = promisify(fs.stat);

app.get('/video', async (req, res) => {
  try {
    const videoPath = './public/myvideo.mp4';
    const videoSize = (await stat(videoPath)).size;

    // Extract the start and end positions

    // Send the chunked data
  } catch {
    // Handle exception
  }
});

Now we read and stream the chunk:

const CHUNK_SIZE = 10 ** 6; // 1MB

app.get('/video', async (req, res) => {
   //...
   const start = Number(range.replace(/ytes=/, '').split('-')[0]);
   const end = Math.min(start + CHUNK_SIZE, videoSize - 1);

   //Specify the chunk headers for the response

   const contentLength = end - start + 1;
   const headers = {
     'Content-Range': `bytes ${start}-${end}/${videoSize}`,
     'Accept-Ranges': 'bytes',
     'Content-Length': contentLength,
     'Content-Type': 'video/mp4',
   };

   res.writeHead(206, headers);

   const videoStream = fs.createReadStream(videoPath, { start, end });

   videoStream.pipe(res);
});

Error Handling and Enhancements

Error handling is crucial.

app.get('/video', async (req, res) => {
  try {
    // Implementing video streaming
  } catch (err) {
    console.error(err);
    res.sendStatus(500);
  }
});

You can also enhance this by implementing an adaptive streaming protocol such as HLS or DASH.

Adding Frontend Logic

To request the video from the frontend, you modify your video’s src attribute to point to the new route:

<video id='videoPlayer' controls></video>

<script>
document.getElementById('videoPlayer').src = '/video';
</script>

With this, compatible browsers will automatically request the correct video chunks as needed.

Conclusion

In this article, we learned how to create an efficient and modern video streaming service using Node.js and Express. We made use of the Range HTTP header for partial requests to implement true video streaming, and covered basic practices for setting up an Express server, handling video data, implementing error handling, and indicating streaming logic in the frontend. Look forward to implementing HLS or DASH for an adaptive streaming experience.