Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Streaming a video file to an html5 video player with Node.js so that the video controls continue to work?

Tl;Dr - The Question:

What is the right way to handle streaming a video file to an html5 video player with Node.js so that the video controls continue to work?

I think it has to do with the way that the headers are handled. Anyway, here's the background information. The code is a little lengthy, however, it's pretty straightforward.

Streaming small video files to HTML5 video with Node is easy

I learned how to stream small video files to an HTML5 video player very easily. With this setup, the controls work without any work on my part, and the video streams flawlessly. A working copy of the fully working code with sample video is here, for download on Google Docs.

Client:

<html>   <title>Welcome</title>     <body>       <video controls>         <source src="movie.mp4" type="video/mp4"/>         <source src="movie.webm" type="video/webm"/>         <source src="movie.ogg" type="video/ogg"/>         <!-- fallback -->         Your browser does not support the <code>video</code> element.     </video>   </body> </html> 

Server:

// Declare Vars & Read Files  var fs = require('fs'),     http = require('http'),     url = require('url'),     path = require('path'); var movie_webm, movie_mp4, movie_ogg; // ... [snip] ... (Read index page) fs.readFile(path.resolve(__dirname,"movie.mp4"), function (err, data) {     if (err) {         throw err;     }     movie_mp4 = data; }); // ... [snip] ... (Read two other formats for the video)  // Serve & Stream Video  http.createServer(function (req, res) {     // ... [snip] ... (Serve client files)     var total;     if (reqResource == "/movie.mp4") {         total = movie_mp4.length;     }     // ... [snip] ... handle two other formats for the video     var range = req.headers.range;     var positions = range.replace(/bytes=/, "").split("-");     var start = parseInt(positions[0], 10);     var end = positions[1] ? parseInt(positions[1], 10) : total - 1;     var chunksize = (end - start) + 1;     if (reqResource == "/movie.mp4") {         res.writeHead(206, {             "Content-Range": "bytes " + start + "-" + end + "/" + total,                 "Accept-Ranges": "bytes",                 "Content-Length": chunksize,                 "Content-Type": "video/mp4"         });         res.end(movie_mp4.slice(start, end + 1), "binary");     }     // ... [snip] ... handle two other formats for the video }).listen(8888); 

But this method is limited to files < 1GB in size.

Streaming (any size) video files with fs.createReadStream

By utilizing fs.createReadStream(), the server can read the file in a stream rather than reading it all into memory at once. This sounds like the right way to do things, and the syntax is extremely simple:

Server Snippet:

movieStream = fs.createReadStream(pathToFile); movieStream.on('open', function () {     res.writeHead(206, {         "Content-Range": "bytes " + start + "-" + end + "/" + total,             "Accept-Ranges": "bytes",             "Content-Length": chunksize,             "Content-Type": "video/mp4"     });     // This just pipes the read stream to the response object (which goes      //to the client)     movieStream.pipe(res); });  movieStream.on('error', function (err) {     res.end(err); }); 

This streams the video just fine! But the video controls no longer work.

like image 443
WebDeveloper404 Avatar asked Jul 26 '14 22:07

WebDeveloper404


People also ask

Is Nodejs suitable for video streaming?

Nodejs is very good to streaming audio and video, but nodejs is a new technology, so it's don't have a lot of softwares yet.

How does HTML5 video streaming work?

HTML5 is not itself a streaming protocol. Websites built with HTML5 can use several different streaming protocols to play video, including HTTP live streaming (HLS) and MPEG-DASH. This is configured on the server side, not in the HTML markup code.

What is HTTP streaming in node JS?

What are Streams? Streams are objects that let you read data from a source or write data to a destination in continuous fashion. In Node.js, there are four types of streams − Readable − Stream which is used for read operation. Writable − Stream which is used for write operation.


1 Answers

The Accept Ranges header (the bit in writeHead()) is required for the HTML5 video controls to work.

I think instead of just blindly send the full file, you should first check the Accept Ranges header in the REQUEST, then read in and send just that bit. fs.createReadStream support start, and end option for that.

So I tried an example and it works. The code is not pretty but it is easy to understand. First we process the range header to get the start/end position. Then we use fs.stat to get the size of the file without reading the whole file into memory. Finally, use fs.createReadStream to send the requested part to the client.

var fs = require("fs"),     http = require("http"),     url = require("url"),     path = require("path");  http.createServer(function (req, res) {   if (req.url != "/movie.mp4") {     res.writeHead(200, { "Content-Type": "text/html" });     res.end('<video src="http://localhost:8888/movie.mp4" controls></video>');   } else {     var file = path.resolve(__dirname,"movie.mp4");     fs.stat(file, function(err, stats) {       if (err) {         if (err.code === 'ENOENT') {           // 404 Error if file not found           return res.sendStatus(404);         }       res.end(err);       }       var range = req.headers.range;       if (!range) {        // 416 Wrong range        return res.sendStatus(416);       }       var positions = range.replace(/bytes=/, "").split("-");       var start = parseInt(positions[0], 10);       var total = stats.size;       var end = positions[1] ? parseInt(positions[1], 10) : total - 1;       var chunksize = (end - start) + 1;        res.writeHead(206, {         "Content-Range": "bytes " + start + "-" + end + "/" + total,         "Accept-Ranges": "bytes",         "Content-Length": chunksize,         "Content-Type": "video/mp4"       });        var stream = fs.createReadStream(file, { start: start, end: end })         .on("open", function() {           stream.pipe(res);         }).on("error", function(err) {           res.end(err);         });     });   } }).listen(8888); 
like image 138
tungd Avatar answered Sep 19 '22 03:09

tungd