1

I am using Mongodb to store video files as grid fs. It surprised me today when I came to know that video is not playing on Safari browser. However video read from Gridfs is playing fine on Chrome & Firefox. Following are two approach to read video files back from Grid fs. Both approach has same problem. I do the that correct mime type is getting set. Approach 1:

exports.previewFile = function (req, res) {
    var contentId = new DBModule.BSON.ObjectID(req.params.fileid);
    log.debug('Calling previewFile inside FileUploadService for content id ' + contentId);
    //Read metadata details from fs.files
    var query = {_id: contentId};
    documentOperationModule.getDocumentByQuery(query, constants.FS_FILES_COLLECTION, function (err, files) {
        if (!Utilities.isEmptyList(files)) {
            var fileObj = files[0];
            var gridStore = DBModule.db.gridStore(contentId, 'r');
            gridStore.open(function (err, gridStore) {
                var stream = gridStore.stream(true);
                if (!Utilities.isEmptyObject(fileObj.metadata)) {
                    res.setHeader('Content-Type', fileObj.metadata.contentType);
                }
                stream.on("data", function (chunk) {
                    log.debug("Chunk of file data");

                    res.write(chunk);
                });

                stream.on("end", function () {
                    log.debug("EOF of file");
                    res.end();
                });

                stream.on("close", function () {
                    log.debug("Finished reading the file");
                });
            });          
        } else {
            log.error({err: err}, 'Failed to read the content for id ' + contentId);
            res.status(constants.HTTP_CODE_INTERNAL_SERVER_ERROR);
            res.json({error: contentId + " not found"});
        }

    });
};

Approach 2:

exports.previewFile = function (req, res) {
    var contentId = new DBModule.BSON.ObjectID(req.params.fileid);
    log.debug('Calling previewFile inside FileUploadService for content id ' + contentId);
    //Read metadata details from fs.files
    var query = {_id: contentId};
    documentOperationModule.getDocumentByQuery(query, constants.FS_FILES_COLLECTION, function (err, files) {
        if (!Utilities.isEmptyList(files)) {
            var fileObj = files[0];
            var gridStore = DBModule.db.gridStore(contentId, 'r');        

             gridStore.read(function (err, data) {
             if (!err) {
             if (!Utilities.isEmptyObject(fileObj.metadata)) {
             res.setHeader('Content-Type', fileObj.metadata.contentType);
             }
             res.end(data);
             } else {
             log.error({err: err}, 'Failed to read the content for id ' + contentId);
             res.status(constants.HTTP_CODE_INTERNAL_SERVER_ERROR);
             res.json({error: err});
             }                
             }); 
        } else {
            log.error({err: err}, 'Failed to read the content for id ' + contentId);
            res.status(constants.HTTP_CODE_INTERNAL_SERVER_ERROR);
            res.json({error: contentId + " not found"});
        }

    });
};

Following is screen of Safari for reference. enter image description here Please help

joy
  • 3,669
  • 7
  • 38
  • 73
  • You need to support byte-range requests. Safari will request only parts of the video at a time by specifying the range of bytes it wants. Your server needs to handle those range requests and only respond with the range of bytes from the video that it wants. – idbehold Jul 01 '15 at 14:58
  • How to support byte-range requests? Please share some direction on this. – joy Jul 01 '15 at 15:30
  • See [here for a similar question and answer](http://stackoverflow.com/a/14306558/1397319), and see [here](http://www.schillmania.com/projects/soundmanager2/doc/technotes/#byte-serving) and [here](https://benramsey.com/blog/2008/05/206-partial-content-and-range-requests/) for more information on the subject. – idbehold Jul 01 '15 at 16:31

1 Answers1

0

Try this GIST (by https://gist.github.com/psi-4ward)

It makes use of the byte range header

https://gist.github.com/psi-4ward/7099001

Although it does not work for me with safari, it makes sure that the correct hears are set and the correct content is delivered. It could narrow down your problem

EDIT

I've updated the GIST. It works now fine with Safari for me

https://gist.github.com/derMani/218bd18cc926d85a57a1

This should solve your problem

function StreamGridFile(req, res, GridFile) {
  if(req.headers['range']) {
    // Range request, partialle stream the file
    console.log('Range Reuqest');
    var parts = req.headers['range'].replace(/bytes=/, "").split("-");
    var partialstart = parts[0];
    var partialend = parts[1];


    var start = parseInt(partialstart, 10);

    var end = partialend ? parseInt(partialend, 10) : GridFile.length -1;
    var chunksize = (end-start)+1;

    res.writeHead(206, {
      'Content-disposition': 'filename=xyz',
      'Accept-Ranges': 'bytes',
      'Content-Type': GridFile.contentType,
      'Content-Range': 'bytes ' + start + '-' + end + '/' + GridFile.length,
      'Content-Length': chunksize
    });

    // Set filepointer
    GridFile.seek(start, function() {
      // get GridFile stream
      var stream = GridFile.stream(true);

      // write to response
      stream.on('data', function(buff) {
        // count data to abort streaming if range-end is reached
        // perhaps theres a better way?
        if(start >= end) {
          // enough data send, abort
          GridFile.close();
          res.end();
        } else {
          res.write(buff);
        }
      });
    });

  } else {

    // stream back whole file
    console.log('No Range Request');
    res.header('Content-Type', GridFile.contentType);
    res.header('Content-Length', GridFile.length);
    var stream = GridFile.stream(true);
    stream.pipe(res);
  }
}

Regards Rolf

Rolf Beh
  • 347
  • 3
  • 5
  • 14
  • You should probably check to make sure the `GridFile.length` isn't less than `start` and if it is: return a 409 status. Also you should `end = Math.min(GridFile.length, end);` before you send the headers. Aside from that the only (complicated) thing you're missing is multi-byte-range support. – idbehold Jul 22 '15 at 20:47