Sling Academy
Home/Node.js/Node.js + Express: How to stream a video to client side

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

Last updated: December 30, 2023

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.

Next Article: Express + Handlebars: Escaping Sensitive HTML Output & JavaScript

Previous Article: Node.js + Express: How to return CSV files

Series: Node.js & Express Tutorials

Node.js

You May Also Like

  • NestJS: How to create cursor-based pagination (2 examples)
  • Cursor-Based Pagination in SequelizeJS: Practical Examples
  • MongooseJS: Cursor-Based Pagination Examples
  • Node.js: How to get location from IP address (3 approaches)
  • SequelizeJS: How to reset auto-increment ID after deleting records
  • SequelizeJS: Grouping Results by Multiple Columns
  • NestJS: Using Faker.js to populate database (for testing)
  • NodeJS: Search and download images by keyword from Unsplash API
  • NestJS: Generate N random users using Faker.js
  • Sequelize Upsert: How to insert or update a record in one query
  • NodeJS: Declaring types when using dotenv with TypeScript
  • Using ExpressJS and Multer with TypeScript
  • NodeJS: Link to static assets (JS, CSS) in Pug templates
  • NodeJS: How to use mixins in Pug templates
  • NodeJS: Displaying images and links in Pug templates
  • ExpressJS + Pug: How to use loops to render array data
  • ExpressJS: Using MORGAN to Log HTTP Requests
  • NodeJS: Using express-fileupload to simply upload files
  • ExpressJS: How to render JSON in Pug templates