{"id":2212,"date":"2022-01-25T17:24:54","date_gmt":"2022-01-25T17:24:54","guid":{"rendered":"https:\/\/lvboard.infostore.in.ua\/?p=2212"},"modified":"2022-01-25T17:24:54","modified_gmt":"2022-01-25T17:24:54","slug":"build-a-video-streaming-server-with-node-js","status":"publish","type":"post","link":"https:\/\/lvboard.infostore.in.ua\/?p=2212","title":{"rendered":"Build a video streaming server with Node.js"},"content":{"rendered":"\n<p>When building web apps, developers frequently have to handle different types of media, some of which can be complex. In this article, we\u2019ll create our own video streaming server using Node.js.<\/p>\n\n\n\n<!--more-->\n\n\n\n<p>If you follow this tutorial step-by-step, you\u2019ll be able to build a video streaming server with Node.js that you can integrate into your own project. To follow along with this article, you can&nbsp;<a href=\"https:\/\/github.com\/thesmartcoder7\/video_streaming_server\">check out the GitHub repo<\/a>.<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter\"><img src=\"https:\/\/blog.logrocket.com\/wp-content\/uploads\/2022\/01\/video-streaming-server-node-1.png\" alt=\"Video Streaming Server Node\" class=\"wp-image-87922\"\/><\/figure><\/div>\n\n\n\n<h2>Project overview<\/h2>\n\n\n\n<p>Before we begin coding our project, let\u2019s review how our app will work at a high-level. In the image above, the browser is on the left and the server is on the right. On your site, you\u2019ll have an HTML5&nbsp;<code>video<\/code>&nbsp;element with a source that points to the&nbsp;<code>\/video<\/code>&nbsp;endpoint.<\/p>\n\n\n\n<p>First, the&nbsp;<code>video<\/code>&nbsp;element makes a request to the server, then the header provides the desired range of bytes from the video. For example, at the beginning of the video, the requested range would be from the 0th byte onwards, hence the&nbsp;<code>0-<\/code>. The server will respond with a&nbsp;<code>206<\/code>&nbsp;HTTP status, indicating it is returning partial content with the proper header response, which includes the range and content length.<\/p>\n\n\n\n<p>The response headers indicate to the&nbsp;<code>video<\/code>&nbsp;element that the video is incomplete. As a result, the&nbsp;<code>video<\/code>&nbsp;element will play what it has downloaded so far. When this happens, the&nbsp;<code>video<\/code>&nbsp;element will continue making requests, and the cycle will continue until there are no bytes left.<\/p>\n\n\n\n<h3>Application pros and cons<\/h3>\n\n\n\n<p>Now that we understand how our app will work, let\u2019s consider some of the pros and cons of following this methodology.<\/p>\n\n\n\n<p>As you may have guessed from the application overview, our streaming server will be fairly simple to implement. Essentially, we\u2019re creating a file system and returning it back to the client. Our server will allow us to select timeframes throughout the video and decide how big of a payload to send back. For mine, I chose 1MB, but you have the freedom to play around with it.<\/p>\n\n\n\n<p>However, because of our app\u2019s simplicity, the server and video player don\u2019t work as well together as we would like. Essentially, the video player will just request the part of the video you\u2019re on, without taking into account what you already requested. It\u2019s likely that you\u2019ll end up requesting some of the same resources over and over again.<\/p>\n\n\n\n<h2>Getting started<\/h2>\n\n\n\n<p>First, we\u2019ll set up a new folder and initialize npm:<a href=\"https:\/\/blog.logrocket.com\/build-video-streaming-server-node\/\"><\/a><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">npm init<\/pre>\n\n\n\n<p>Now, install Express and nodemon:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">npm install --save express nodemon<\/pre>\n\n\n\n<p>Given that your&nbsp;<code>video<\/code>&nbsp;element is an empty folder, you\u2019ll need to generate an HTML file as follows:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&amp;lt;!DOCTYPE html&gt;\n&lt;html lang=\"en\"&gt;\n    &lt;head&gt;\n        &lt;meta charset=\"UTF-8\" \/&gt;\n        &lt;meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" \/&gt;\n        &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \/&gt;\n        &lt;title&gt;Video Streaming With Node&lt;\/title&gt;\n        &lt;style&gt;\n            body {\n                margin: 5% auto;\n                max-width: 100%;\n                background-color: rgb(14, 14, 14);\n                padding-top: 10%;\n                padding-left: 35%;\n            }\n        &lt;\/style&gt;\n    &lt;\/head&gt;\n    &lt;body&gt;\n        &lt;video id=\"videoPlayer\" width=\"50%\" controls muted=\"muted\" autoplay&gt;\n            &lt;source src=\"\/video\" type=\"video\/mp4\" \/&gt;\n        &lt;\/video&gt;\n    &lt;\/body&gt;\n&lt;\/html&gt;<\/pre>\n\n\n\n<h2>Writing the&nbsp;<code>\/video<\/code>&nbsp;endpoint<\/h2>\n\n\n\n<p>Next, we\u2019ll write the&nbsp;<code>\/video<\/code>&nbsp;endpoint. Eventually, when you test the HTML code above, you should have a&nbsp;<code>media<\/code>&nbsp;element on the screen.<\/p>\n\n\n\n<p>For this to work, we\u2019ll first need to create a new JavaScript file that will house all our functions. In this new file, we\u2019ll import Express and&nbsp;<code>fs<\/code>, which stands for file system.&nbsp;<code>fs<\/code>&nbsp;will create a file stream, then return it to the client in the&nbsp;<code>\/video<\/code>&nbsp;endpoint. Run the code below:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">const express = require(\"express\");\nconst app = express();\nconst fs = require(\"fs\");\n\napp.get(\"\/\", function (req, res) {\n    res.sendFile(__dirname + \"\/index.html\");\n});\n\n \/\/ more code will go in here just befor the listening function\n\napp.listen(8000, function () {\n    console.log(\"Listening on port 8000!\");\n});<\/pre>\n\n\n\n<p>Now, we\u2019ll create a function for the&nbsp;<code>\/video<\/code>&nbsp;endpoint. You need to make sure there is a range header. Otherwise, you won\u2019t be able to tell the client what part of the video you want to send back. The&nbsp;<code>if<\/code>&nbsp;statements handles this, returning a&nbsp;<code>400&nbsp;Error<\/code>&nbsp;alerting the client that it needs a range header:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">app.get(\"\/video\", function (req, res) {\n    const range = req.headers.range;\n    if (!range) {\n        res.status(400).send(\"Requires Range header\");\n    }\n});<\/pre>\n\n\n\n<p>We also need to provide the path and the size of the video. As long as your video is in the same directory as the JavaScript file, there is no need to add a bunch of slashes. However, if the video is not in the same directory as the JavaScript file, you\u2019ll need to provide the relative path, like in the example below:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">const videoPath = \"Chris-Do.mp4\";\nconst videoSize = fs.statSync(\"Chris-Do.mp4\").size;<\/pre>\n\n\n\n<p>Now, the new file should look like the following code block:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">const express = require(\"express\");\nconst app = express();\nconst fs = require(\"fs\");\n\napp.get(\"\/\", function (req, res) {\n    res.sendFile(__dirname + \"\/index.html\");\n});\napp.get(\"\/video\", function (req, res) {\n    const range = req.headers.range;\n    if (!range) {\n        res.status(400).send(\"Requires Range header\");\n    }\n    const videoPath = \"Chris-Do.mp4\";\n    const videoSize = fs.statSync(\"Chris-Do.mp4\").size;\n});\n\napp.listen(8000, function () {\n    console.log(\"Listening on port 8000!\");\n});<\/pre>\n\n\n\n<h2>Parsing the range<\/h2>\n\n\n\n<p>Next, we\u2019ll parse the range, seen in line&nbsp;<code>10<\/code>&nbsp;in the code block above. I\u2019ll give it 1MB at a time, which is known as a chunk size:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">const CHUNK_SIZE = 10 ** 6; \/\/ 1MB\nconst start = Number(range.replace(\/\\D\/g, \"\"));<\/pre>\n\n\n\n<p>Now, we\u2019ll parse the starting byte from the range headers. Since it is a string, you need to convert it to a number using the line below:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">const start = Number(range.replace(\/\\D\/g, \"\"));<\/pre>\n\n\n\n<p>Notice that I subtract one from the&nbsp;<code>videoSize<\/code>&nbsp;in the end chunk because that is the last byte. If there are 100 bytes in a video, then the 99th byte is the last one because we begin counting from zero in computer science.<\/p>\n\n\n\n<p>Now, you need to calculate the ending byte that you\u2019ll send back. First, add the chunk size, which is 1MB, to the starting chunk. As the server continues sending back 1MB to the starting chunk, eventually, the total size of the bytes sent could surpass the size of the video itself.<\/p>\n\n\n\n<p>In this case, you\u2019ll need to return the video size. You can do so using the&nbsp;<code>Math.min<\/code>&nbsp;function, which takes the minimum of the two parameters given, summarized by the line below:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">const end = Math.min(start + CHUNK_SIZE, videoSize - 1);<\/pre>\n\n\n\n<h2>Creating response headers<\/h2>\n\n\n\n<p>Now, we need to create the response headers that we\u2019ll return. First, calculate the content length with&nbsp;<code>end-start&nbsp;+&nbsp;1<\/code>.<\/p>\n\n\n\n<p>Then, we\u2019ll create the&nbsp;<code>headers<\/code>&nbsp;object. In the content range, you need to use the starting byte, the end byte, and the video size, as follows:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">const headers = {\n    \"Content-Range\": `bytes ${start}-${end}\/${videoSize}`,\n    ... \/\/ this ... just indicates that there is more code here. \n        \/\/ it is not part of code.\n}<\/pre>\n\n\n\n<p>With the code above, the video player knows how far along it is based on the video size itself. After that, we\u2019ll specify the type of data we\u2019ll send back. Add the content length and the video type. Your&nbsp;<code>headers<\/code>&nbsp;object should look like the code below:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">const headers = {\n    \"Content-Range\": `bytes ${start}-${end}\/${videoSize}`,\n    \"Accept-Ranges\": \"bytes\",\n    \"Content-Length\": contentLength,\n    \"Content-Type\": \"video\/mp4\",\n};<\/pre>\n\n\n\n<p>Now, we need to write a response for the request. I am using&nbsp;<code>206<\/code>&nbsp;as the status, indicating that I\u2019m sending partial content. With this, you should also set the headers as follows:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">\/\/ HTTP Status 206 for Partial Content\nres.writeHead(206, headers);<\/pre>\n\n\n\n<p>We need to use the file system library to create the&nbsp;<code>readstream<\/code>, using the video path as an argument and the&nbsp;<code>start<\/code>&nbsp;and&nbsp;<code>end<\/code>&nbsp;as an options in the&nbsp;<code>options<\/code>&nbsp;object:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">const videoStream = fs.createReadStream(videoPath, { start, end });<\/pre>\n\n\n\n<p><code>videoStream<\/code>&nbsp;does not do anything by itself. We need to pipe it into the response we had at the start of the function:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">videoStream.pipe(res);<\/pre>\n\n\n\n<p>If you\u2019ve been following step-by-step, your file should look like the following code:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">const express = require(\"express\");\nconst app = express();\nconst fs = require(\"fs\");\n\napp.get(\"\/\", function (req, res) {\n    res.sendFile(__dirname + \"\/index.html\");\n});\n\napp.get(\"\/video\", function (req, res) {\n    const range = req.headers.range;\n    if (!range) {\n        res.status(400).send(\"Requires Range header\");\n    }\n    const videoPath = \"Chris-Do.mp4\";\n    const videoSize = fs.statSync(\"Chris-Do.mp4\").size;\n    const CHUNK_SIZE = 10 ** 6;\n    const start = Number(range.replace(\/\\D\/g, \"\"));\n    const end = Math.min(start + CHUNK_SIZE, videoSize - 1);\n    const contentLength = end - start + 1;\n    const headers = {\n        \"Content-Range\": `bytes ${start}-${end}\/${videoSize}`,\n        \"Accept-Ranges\": \"bytes\",\n        \"Content-Length\": contentLength,\n        \"Content-Type\": \"video\/mp4\",\n    };\n    res.writeHead(206, headers);\n    const videoStream = fs.createReadStream(videoPath, { start, end });\n    videoStream.pipe(res);\n});\n\napp.listen(8000, function () {\n    console.log(\"Listening on port 8000!\");\n});<\/pre>\n\n\n\n<p>Before wrapping up, you simply need to add&nbsp;<code>\"start\":&nbsp;\"nodemon index.js\"<\/code>&nbsp;to your&nbsp;<code>package.json<\/code>&nbsp;file:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">\"scripts\": {\n      \"start\": \"nodemon index.js\" \/\/this is the main line you need to add\n},\n\n\/\/note that the index.js is just the name of my file. yours might be named differently<\/pre>\n\n\n\n<p>To see the final output, simply run&nbsp;<code>npm start<\/code>.<\/p>\n\n\n\n<h2>Conclusion<\/h2>\n\n\n\n<p>In this tutorial, we learned to build our own video streaming server using Node.js. First, we covered the project architecture in depth, then we elaborated on the pros and cons of following a simple methodology. Then, we build our app by creating the&nbsp;<code>\/video<\/code>&nbsp;endpoint, parsing the range, and creating the response headers.<\/p>\n\n\n\n<p>By following the steps in this tutorial, you can build your own Node.js video streaming server that you can integrate into your own application. I hope you enjoyed this article!<\/p>\n\n\n\n<h2>200\u2019s only&nbsp;<img src=\"https:\/\/blog.logrocket.com\/wp-content\/uploads\/2019\/10\/green-check.png\">&nbsp;Monitor failed and slow network requests in production<\/h2>\n\n\n\n<p>Deploying a Node-based web app or website is the easy part. Making sure your Node instance continues to serve resources to your app is where things get tougher. If you\u2019re interested in ensuring requests to the backend or third party services are successful,&nbsp;<a href=\"https:\/\/logrocket.com\/signup\/\" target=\"_blank\" rel=\"noreferrer noopener\">try LogRocket<\/a>.&nbsp;<a href=\"https:\/\/logrocket.com\/signup\/\" target=\"_blank\" rel=\"noreferrer noopener\"><\/a><a href=\"https:\/\/logrocket.com\/signup\/\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/logrocket.com\/signup\/<\/a><\/p>\n\n\n\n<p><a href=\"https:\/\/logrocket.com\/signup\/\" target=\"_blank\" rel=\"noreferrer noopener\">LogRocket<\/a>&nbsp;is like a DVR for web and mobile apps, recording literally everything that happens while a user interacts with your app. Instead of guessing why problems happen, you can aggregate and report on problematic network requests to quickly understand the root cause.LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions\/state.&nbsp;<a href=\"https:\/\/logrocket.com\/signup\/\" target=\"_blank\" rel=\"noreferrer noopener\">Start monitoring for free<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>When building web apps, developers frequently have to handle different types of media, some of which can be complex. In this article, we\u2019ll create our own video streaming server using Node.js.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[30],"tags":[94],"_links":{"self":[{"href":"https:\/\/lvboard.infostore.in.ua\/index.php?rest_route=\/wp\/v2\/posts\/2212"}],"collection":[{"href":"https:\/\/lvboard.infostore.in.ua\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/lvboard.infostore.in.ua\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/lvboard.infostore.in.ua\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/lvboard.infostore.in.ua\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2212"}],"version-history":[{"count":1,"href":"https:\/\/lvboard.infostore.in.ua\/index.php?rest_route=\/wp\/v2\/posts\/2212\/revisions"}],"predecessor-version":[{"id":2213,"href":"https:\/\/lvboard.infostore.in.ua\/index.php?rest_route=\/wp\/v2\/posts\/2212\/revisions\/2213"}],"wp:attachment":[{"href":"https:\/\/lvboard.infostore.in.ua\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2212"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/lvboard.infostore.in.ua\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2212"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/lvboard.infostore.in.ua\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2212"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}