0

I am developing an online course application. Once a user is enrolled in a course, then that user can go to the home page of course, and then click on any video lecture to view it. To stream video which is in .mp4 format, I am using socket.io. Here is what i'm doing right now.

router.get('/learn/:courseName/lecture/:video', isAuthenticated, function (req, res) {

    // var file = path.resolve(__dirname,"movie.mp4");

    console.log('courseName is: ' + req.params.courseName);
    console.log('video name is: ' + req.params.video);

    var file = "/home/mobileedx/WebstormProjects/passportAuthentication/videoLecture/movie.mp4";
    console.log('file is: ' + file);
    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;
        console.log('value of range is: ' + range);
        if(!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);
            });
        console.log('stream value is: ' + stream);
    });
});

However when I try to access it from browser, I get an error in the browser as "Range not Satisfiable". I am using google chrome and mozilla firefox for testing.

range not Satisfiable

I'm logging the req.headers.range and I get its value as 'undefined'. Here it is. undefined value for range.

Here is the request and response header, when we access it through browser. You can notice, there is no 'Range' header in the GET request.

Request and Response header

Please let me know, what's going wrong here. Because, If I run the same code as a standalone application, then it works.

Scalaboy
  • 163
  • 1
  • 12
  • I'm not understanding how you are using socket.io. I am trying to figure out how to send a video feed over webSockets but, just like your code shows, there is no websockets. It's being sent via express. – John Sep 15 '22 at 04:24

2 Answers2

0

As you probably are aware, the error message you are getting is being generated from your code directly because the range value is not defined - these lines send it back:

var range = req.headers.range;
        console.log('value of range is: ' + range);
        if(!range) {
            return res.sendStatus(416);
        }

A 416 error will generate the message you show.

I think the problem is that your code is assuming that it is an error to receive a HTTP request without a range request, but this is actually an optional parameter:

HTTP retrieval requests using conditional or unconditional GET methods MAY request one or more sub-ranges of the entity, instead of the entire entity, using the Range request header (http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.2)

Its normal for the first request from the browser to not have any value for range. When the server responds to indicate it accepts range requests the browser will then send another request with a range included.

There is a really useful example of the flow in this answer here: https://stackoverflow.com/a/8507991/334402

If you want to use Express static serving functionality for this, as it support ranges, then it is well documented here: https://github.com/expressjs/serve-static

Example code from the above link:

var finalhandler = require('finalhandler')
var http = require('http')
var serveStatic = require('serve-static')

// Serve up public/ftp folder
var serve = serveStatic('public/ftp', {'index': ['index.html', 'index.htm']})

// Create server
var server = http.createServer(function onRequest (req, res) {
  serve(req, res, finalhandler(req, res))
})

// Listen
server.listen(3000)

Update

Here is a very simply node.js video server.

You just need to create a videos directory in the same folder as app.js and put the video there.

If your video is called BigBuckBunny.mp4 you access it via the URL http://localhost:3000/BigBuckBunny.mp4

var express = require('express')
var serveStatic = require('serve-static')

var app = express()

app.use(serveStatic(__dirname + '/videos'))

app.listen(3000)
Community
  • 1
  • 1
Mick
  • 24,231
  • 1
  • 54
  • 120
  • I understand that the error is coming from my code because range header is not set in the first request, however, that's usually the case when you send the first request. I have updated my question with the request and response header. You can see that server doesn't send any 'Accept-Ranges' header either. I'm clueless, what's missing or how should I add a Range header in the first request so that in the subsequent response 'Accept-Ranges' will be added by the server. – Scalaboy Nov 16 '16 at 12:36
  • I think the first thing I would do is remove the current code which is sending a 416 in response to the first request - this is effectively ending the communication as far as the browser is concerned. Personally I have used the Express static mechanism to stream video and that will work simply and with accept-ranges as standard. I'll add a link and example above. – Mick Nov 16 '16 at 14:06
  • I'm little confused with your example, so how do you think the video file will be sent to the server. Do you mean, we need to give link of the video file in index.html? I was also thinking if there is any 3rd party npm module, which I can use add it in my code and stream the video. – Scalaboy Nov 17 '16 at 02:44
  • Your code in the question is to stream a video from a server to a browser which is what this will do. I think maybe the directory names are confusing in the example so I've added a very simple node app which will allow you stream any video you place in a directory called 'videos'. – Mick Nov 17 '16 at 11:30
0

As of now I have implemented a very naive solution, which I definitely like to improve on. So here is my code, which definitely needs to be optimized. Any 3rd party npm module will also help me.

router.get('/learn/:courseName/lecture/:video', isAuthenticated, function (req, res) {

var filePath = path.join(__dirname, 'movie.mp4');
var stat = fs.statSync(filePath);

res.writeHead(200, {
    'Content-Type': 'audio/mpeg',
    'Content-Length': stat.size
});

var readStream = fs.createReadStream(filePath);
// We replaced all the event handlers with a simple call to util.pump()
util.pump(readStream, res);

});

Please let me know, how I can optimize this solution or my previous code to work. Even any 3rd party npm module will also help me. Thanks.

Scalaboy
  • 163
  • 1
  • 12