How to Code a Video Streaming Server using NodeJS
Abdisalan

Abdisalan @abdisalan_js

About: A software engineer just trying to gain and share knowledge with everyone.

Location:
Boulder
Joined:
Apr 8, 2019

How to Code a Video Streaming Server using NodeJS

Publish Date: Oct 25 '20
434 33

Do you want to stream video in your app without needing users to download the entire video? Here's how to do exactly that using NodeJS.

Final Result

Here's the end result of what we're gonna make.
Video player with buffering timeline

Notice that light grey bar on the video timeline? That's the HTML5 Video Element buffering the video from our NodeJS server!

If you want to git clone the code and play with it yourself, here's the link to my GitHub Repo! https://github.com/Abdisalan/blog-code-examples/tree/master/http-video-stream

Part 1: Setup npm project

You'll need to install NodeJS and run:

mkdir http-video-stream
cd http-video-stream
npm init
npm install --save express nodemon
Enter fullscreen mode Exit fullscreen mode

Part 2: index.html

We need to create a HTML5 Video element, and set the source as "/video", which is where server's endpoint is.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>HTTP Video Stream</title>
  </head>
  <body>
    <video id="videoPlayer" width="650" controls muted="muted" autoplay>
      <source src="/video" type="video/mp4" />
    </video>
  </body>
</html>

Enter fullscreen mode Exit fullscreen mode

Part 3: index.js

Now lets setup our node server so that on "/" endpoint it serves our index.html page.

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

app.get("/", function (req, res) {
  res.sendFile(__dirname + "/index.html");
});

app.listen(8000, function () {
  console.log("Listening on port 8000!");
});

Enter fullscreen mode Exit fullscreen mode

Part 4: package.json -- Run our server

Add a start script to your package.json so that we can run our server using npm start command.
There's more in your package.json file but I just want you to copy this start script. It uses nodemon to run index.js and restarts the server every time you save the index.js file so you don't need to restart the server yourself!

{
  "scripts": {
    "start": "nodemon index.js"
  }
}
Enter fullscreen mode Exit fullscreen mode

Now you should be able to run

npm start
Enter fullscreen mode Exit fullscreen mode

and see our app running on port 8000. Open your browser and go to http://localhost:8000 to see if it worked.

Part 5: index.js (Again)

We're almost there!
For this final stage, you'll need to either find an mp4 video file, or download the one I've provided in my GitHub project link.
https://github.com/Abdisalan/blog-code-examples/tree/master/http-video-stream
Here's the "/video" endpoint for our server.

// in the imports above
const fs = require("fs");

app.get("/video", function (req, res) {
  // Ensure there is a range given for the video
  const range = req.headers.range;
  if (!range) {
    res.status(400).send("Requires Range header");
  }

  // get video stats (about 61MB)
  const videoPath = "bigbuck.mp4";
  const videoSize = fs.statSync("bigbuck.mp4").size;

  // Parse Range
  // Example: "bytes=32324-"
  const CHUNK_SIZE = 10 ** 6; // 1MB
  const start = Number(range.replace(/\D/g, ""));
  const end = Math.min(start + CHUNK_SIZE, videoSize - 1);

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

  // HTTP Status 206 for Partial Content
  res.writeHead(206, headers);

  // create video read stream for this particular chunk
  const videoStream = fs.createReadStream(videoPath, { start, end });

  // Stream the video chunk to the client
  videoStream.pipe(res);
});
Enter fullscreen mode Exit fullscreen mode

The HTML5 video element makes a request to the /video endpoint, and the server returns a file stream of the video, along with headers to tell which part of the video we're sending over.

For a chunk size, I've decided 1MB but you could change that to whatever you like! Another great benefit of this is that we don't need to code the stream to continuously deliver the video data, the browser handles that gracefully for us.

For an in-depth line by line playback on how this works, consider watching my YouTube video on this topic.

Now, you've got a working video streaming server using NodeJS!

Happy Streaming! ✌

Comments 33 total

  • Abdisalan
    AbdisalanOct 25, 2020

    Wow I never knew, that’s cool!

  • Diego Giuliani
    Diego GiulianiOct 28, 2020

    wouldn't that require extra processing power? I thought what they did was generate multiple versions of the file and pick one accordingly to the required bandwidth and quality

  • Diego Giuliani
    Diego GiulianiOct 28, 2020

    right, makes sense. That's what happens when you have a small hiccup in the network and the stream lowers the quality for a moment and resets back to a better quality when it gets better.
    Thanks for your reply.

  • Patricio
    PatricioNov 25, 2020

    Hey Abdisalan! Great one!
    I made the project from scratch from the article, and then noticed it was missing the "require fs". in the repo that's all ok, just the article misses it.
    Other than that, perfect start. I'm starting my journey into video and streaming, and was an excellent start! Is this a main interest of yours?

    • Abdisalan
      AbdisalanNov 26, 2020

      I love it! Glad to have helped you get started! I'll try to fix that issue!

  • Renato Francia Castillo
    Renato Francia CastilloDec 2, 2020

    Nice! Love that you used Node instead of some middleware. Keep it up Abdi!

  • xubmuajkub
    xubmuajkubJan 5, 2021

    Great work! Can you also do the example of play video from remote? e.g example.com/video.mp4

  • Prerna-Pahwa
    Prerna-PahwaJan 14, 2021

    Nice article...but I am unable to stream the video. It's showing just blank with the disabled play button. pls refer the screenshot i attached below.

    • omadoyeabraham
      omadoyeabrahamAug 2, 2021

      @prernapahwa I had a similar issue and the problem for me ended up being that the options object passed to fs.createReadStream as the 2nd parameter requires the keys to be 'start' and 'end'.

      So if you renamed the start and end variables from the tutorial (like i did), you'll need to explicitly pass them to the createReadStream call, e.g.

      fs.createReadStream(videoPath, { start: STARTING_BYTE_POSITION, end: FINAL_BYTE_POSITION });
      
      Enter fullscreen mode Exit fullscreen mode

      NB: STARTING_BYTE_POSITION and FINAL_BYTE_POSITION are the names of my own variables, you'll need to replace them with yours.

      I also noticed that typos in headers also caused the blank video player error you described. Hope this helps.

  • Kanhaiya Kumar
    Kanhaiya KumarJan 22, 2021

    How can we access the URL in frontend if we make this API protected by adding a middleware

  • Son0nline
    Son0nlineFeb 3, 2021

    Pls help, I'm tried with 600MB video
    It ok in localhost but failed in LAN

    • Agastya
      AgastyaMar 14, 2021

      Were you trying it on iOS/Safari by any chance? I faced the problem when trying to make it work on iOS, so I followed this blog.logrocket.com/streaming-video.... Safari-based browsers need a different way of handling things.

  • rhafael bijaksana
    rhafael bijaksanaMay 10, 2021

    thank man. this is very helpful

  • Bharathvaj
    BharathvajJun 9, 2021

    Modern browser like Chrome does this video streaming by default. 🤔

  • andrew ananta
    andrew anantaJun 11, 2021

    wow. this is awesome... thanks for sharing bro

  • Brayan
    BrayanAug 7, 2021

    Hi, I want to do that, but with webtorrent, how do I do that?

  • anandkumar
    anandkumarOct 19, 2021

    I'm not able to stream the video. I'm getting the "Requires Range header" response.
    I tried doing const range = "bytes=0-1023"; but that didnt work too. The player just appears and doesnt play the video

    • gazee
      gazeeMar 19, 2022

      i also got same erroot

      how it is solve .did u got it?

      • Cyber
        CyberApr 21, 2022

        I ran into the same error. The fix seems to be to set your Content-Type header before checking for Range.

  • Riza Dwi
    Riza DwiNov 15, 2021

    Great articles! Really insightful to me since i'm starting curious in streaming

    I wonder that if this stream way uses http protocol, does this mean we use tcp protocol ?

    I just recently learned about tcp & udp protocols
    where one of the usecases of udp is to serve connectionless data transfer (which is perfect in this video streaming case)

  • shahinghasemi
    shahinghasemiJan 23, 2022

    does fs.createReadStream uses sockets under the hood?

  • yas1n67
    yas1n67Feb 4, 2022

    How can play more than one video?

  • yas1n67
    yas1n67Feb 4, 2022

    How can I stream two different videos?

  • Cyber
    CyberApr 21, 2022

    Just came across this post while searching for exactly this, and I'm happy to report that all has gone smoothly! I refactored the code a bit to work with Node's built-in http module, since that's what the rest of my project is using, and made a couple changes to ensure downloading (via the default video player's Download option) works correctly.

  • Elliot
    Elliot May 2, 2022

    Thanks for this. The way it's written works in Chrome but not Safari. To make it work in Safari I had to modify the code from this:

      const CHUNK_SIZE = 10 ** 6; // 1MB
      const start = Number(range.replace(/\D/g, ""));
      const end = Math.min(start + CHUNK_SIZE, videoSize - 1); 
    
    Enter fullscreen mode Exit fullscreen mode

    To this:

       //Safari range header has start and end, vs. Chrome has just start
       let parts = range.split('=')[1]
       let start = Number(parts.split('-')[0])
       let end = Number(parts.split('-')[1])
    
       //For Chrome
       if (!end) {
           const CHUNK_SIZE = 10 ** 6
           end = Math.min(start + CHUNK_SIZE, videoSize - 1)
       } 
    
    Enter fullscreen mode Exit fullscreen mode
  • vrobbi
    vrobbiMay 23, 2022

    Works perfectly with my own .mp4 files, thank you

  • Christopher Sandvik
    Christopher SandvikJun 15, 2022

    "For a chunk size, I've decided 1MB but you could change that to whatever you like!"

    Is there any info on how to decide a chunk size? I've seen 1MB in a few articles but I haven't seen any reasons for it.

  • nima essi
    nima essiApr 4, 2023

    error net::ERR_FILE_NOT_FOUND

Add comment