Building a Video Streaming App with Node.js and Express.js

Building a Video Streaming App with Node.js and Express.js

Introduction

The code is a simple implementation of a video streaming application using the Express framework for Node.js. It sets up an HTTP server that listens on a specified port and responds to various routes with different functionalities.

It uses the 'fs' module to read files from the file system, the 'path' module to handle file path operations, and the 'cors' middleware to enable Cross-Origin Resource Sharing.

Let's explore the code in more detail:

Setting Up the Server

const express = require('express');
const fs = require('fs');
const path = require('path');
const cors = require('cors');
const app = express();
const port = 8080;

app.use(cors());

app.use(express.static(path.join(__dirname, '.')));

app.listen(port, () => console.log(`Video stream app listening on port ${port}!`));

First, we import the required modules, and then we create an instance of the Express application. We also define a port for the server to listen to.

The app.use() function is used to register the middleware. In this case, we're using the 'cors' middleware, which allows cross-origin requests.

The express.static() middleware is used to serve static files, such as HTML, CSS, and JavaScript files, from the directory specified. In this case, we're serving files from the current directory.

Finally, we start the server by calling the app.listen() function and pass it the port number and a callback function that logs a message to the console when the server starts up.

Handling Routes

const filePath = path.join(__dirname, 'video.mp4');

app.get('/ping', (req, res) => res.send('pong !!'));

app.get('/video', (req, res) => {
  try {
    const stat = fs.statSync(filePath);
    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(filePath, { start, end });
      const headers = {
        'Content-Type': 'video/mp4',
        'Content-Length': chunkSize,
        'Content-Range': `bytes ${start}-${end}/${fileSize}`,
        'Accept-Ranges': 'bytes',
      };
      res.status(206).set(headers);
      file.pipe(res);
    } else {
      const headers = {
        'Content-Type': 'video/mp4',
        'Content-Length': fileSize,
        'Accept-Ranges': 'bytes',
      };
      res.status(200).set(headers);
      fs.createReadStream(filePath).pipe(res);
    }
  } catch (err) {
    console.error(err);
    res.status(500).send('Internal server error');
  }
});

app.get('/', (req, res) => res.sendFile(path.join(__dirname, 'index.html')));

The code defines three routes:

  1. /ping - This is a simple route that responds with the string 'pong !!' when it is requested.

  2. /video - This is the main route that streams the video.

  3. / - This is the route that serves the html file.

Streaming Capability

When a client requests the /video route, the code tries to retrieve the requested video file, specified in the filePath variable. The code then retrieves the file's size using the fs.statSync() method.

The code checks if the client has sent a range header in the request headers. The range header specifies the range of bytes that the client wants to receive. If the range header exists, the code sets the start and end positions of the range, calculates the chunk size to be sent, creates a read stream for the specified portion of the file, and sets the appropriate headers in the response to indicate the range of bytes being sent, the content type of the response, and the length of the chunk being sent.

The response status is set to 206 Partial Content to indicate that only a portion of the video is being sent, and the res.status().set().pipe() method chain is used to set the headers and send the video chunk to the client.

If the range header does not exist, the code sets the headers to indicate that the entire video file is being sent and creates a read stream for the entire file, which is piped to the response object using the fs.createReadStream().pipe() method chain.

In the event of an error, such as the video file not being found, the code logs the error to the console and sends a 500 Internal Server Error status code to the client with a descriptive error message.

Overall, the /video route provides video streaming capabilities to the client and is capable of handling requests for partial content, which is useful for resuming playback from a previously viewed position.

To conclude, this code is a great starting point for building more sophisticated video streaming applications using Node.js and can be extended and customized as per the needs of a specific use case.

Here is an example GitHub repository that implements the video streaming app in Node.js and Express.js, based on the code we analyzed in this blog post: github.com/suyash-thakur/video-streaming-app