1

I'm having a problem loading mp4 video files on Safari being served from my nodejs server. The problem does not exist on Chrome.

To illustrate the problem, I have a simple html webpage which reads an mp4 file mov_bbb.mp4 served from w3schools. I then download the same file and serve it from my nodejs server.

vid1 (served from w3schools) loads fine on both Chrome and Safari.

vid2 (served from my nodejs server) load fine on Chrome but not on Safari.

Here's a screenshot of what I see in Chrome:

enter image description here

Here's a screenshot of what I see in Safari:

enter image description here

Here is my html:

<!DOCTYPE html>
<html>
  <head>
    <title>test</title>
  </head>
  <body>
    <video id="vid1" controls preload="auto" src="https://www.w3schools.com/html/mov_bbb.mp4"></video>
    <video id="vid2" controls preload="auto" src="http://localhost:8125/mov_bbb.mp4"></video>
  </body>
</html>

Here is my nodejs server:

var http = require('http');
var fs = require('fs');
var path = require('path');

http.createServer(function (request, response) {
    console.log('request starting...');

    var filePath = '.' + request.url;
    if (filePath == './')
        filePath = './index.html';

    var extname = path.extname(filePath);
    var contentType = 'video/mp4';

    fs.readFile(filePath, function(error, content) {
        if (error) {
            if(error.code == 'ENOENT'){
                fs.readFile('./404.html', function(error, content) {
                    response.writeHead(200, { 'Content-Type': contentType });
                    response.end(content, 'utf-8');
                });
            }
            else {
                response.writeHead(500);
                response.end('Sorry, check with the site admin for error: '+error.code+' ..\n');
                response.end();
            }
        }
        else {
            response.writeHead(200, {
            });
            response.end(content, 'utf-8');
        }
    });

}).listen(8125);
console.log('Server running at http://127.0.0.1:8125/');

Any suggestions would be much appreciated.

Update:

Revised nodejs code. Still same issue on Safari though:

var http = require('http');
var fs = require('fs');

http.createServer(function (request, response) {
  console.log('request starting...');

  var filePath = '.' + request.url;
  if (filePath == './') {
    var contentType = 'text/html';
    filePath = './index.html';
  } else {
    var contentType = 'video/mp4';
  }

  var rstream = fs.createReadStream(filePath);
  rstream.on('error', function(error) {
    response.writeHead(404);
    response.end();
  });
  rstream.on('open', function () {
    response.writeHead(200, {
      'Content-Type': contentType,
    });
  });
  rstream.pipe(response);
}).listen(8125);
console.log('Server running at http://127.0.0.1:8125/');

Also chrome inspector for http headers:

enter image description here

sthomps
  • 4,480
  • 7
  • 35
  • 54

2 Answers2

2

The issue with the video playback has to do with handling the Content-Range range header which must be implemented on the server. Chrome and Safari send different request headers. In my case Chrome sends a single request with range:

bytes=0-

Whereas Safari sends multiple requests:

bytes=0-1
bytes=0-1013150
bytes=197534-1013150

For more information see this thread and for specific code that worked for me see this answer.

Community
  • 1
  • 1
sthomps
  • 4,480
  • 7
  • 35
  • 54
1

First off, don't use readFile() for serving files, it causes unnecessary (potentially high) memory usage because it's loading the entire file into memory. Use fs.createReadStream() and pipe it to the response instead.

Secondly and most importantly, a Content-Type is not being sent in the success case. Also, the Content-Type is wrong in the error case because text/html data is being sent in the response, not video/mp4 data.

Additionally, arbitrary paths should not be passed to filesystem calls because someone could pass a malicious path (including relative paths to get to the filesystem root) and read sensitive information from your filesystem (e.g. private keys, passwords, etc.).

Lastly, if you're going to write a Buffer to a response, you don't need to specify an encoding (e.g. 'utf-8') because the data is already in Buffer form.

mscdex
  • 104,356
  • 15
  • 192
  • 153