3

I am trying for quite some time to stream a video from MongoDB. Read tons of api DOCs and examples just can't get it to work

This is my front-end video Handler :

import { useState, useEffect } from "react";
import ReactPlayer from "react-player";

const CatVideos = () => {
    const [videoList, setvideoList] = useState(null);
    useEffect(() => {
        const getVideos=async ()=> {
            const response = await fetch('http://localhost:8000/cat-videos/videos');
            const data = await response.json();
            setvideoList(data);
            console.log(data);
        }
        getVideos();
    }, [])
    

  return (
    <div>
      <h1>Cat videos</h1>
      {videoList!=null && videoList.map((video)=>{
          return <ReactPlayer key={video._id} url={'http://localhost:8000/cat-videos/videos/'+video._id}/>
      })}
    </div>
  );
};

export default CatVideos;

Backend stream function :

exports.getVideoStream = (req, res, next) => {
  var id = req.params.id;
  let gfs = Grid(conn.db, mongoose.mongo);
  gfs.collection("videos");

  gfs.files
    .findOne({
      _id: mongoose.Types.ObjectId(id),
    })
    .then((result) => {
      const file = result;
      if (!file) {
        return res.status(404).send({
          err: "Unavailable.",
        });
      }

      if (req.headers["range"]) {
        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) : file.length - 1;
        var chunksize = end - start + 1;

        res.header("Accept-Ranges", "bytes");
        res.header("Content-Length", chunksize);
        
        res.header(
          "Content-Range",
          "bytes " + start + "-" + end + "/" + result.length
        );
        console.log(result.contentType)
        res.header("Content-Type", result.contentType);
        gfs.createReadStream({
          _id: result._id,
          range: {
            startPos: start,
            endPos: end,
          },
        }).readStream.pipe(res);
        
      } else {
        console.log("#################before####################");
        res.header("Content-Length", result.length);
        res.header("Content-Type", result.contentType);
        console.log(result._id);
        gfs
          .createReadStream({
            _id: result._id,
          })
          .pipe(res);
      }
    })
    .catch((err) => {
      res.json(err);
    });
};

I do get a response from this function , and it appears that the "Content-Type" remains unchanged.

HTTP/1.1 206 Partial Content
X-Powered-By: Express
Access-Control-Allow-Origin: *
Accept-Ranges: bytes
Date: Thu, 23 Dec 2021 19:38:25 GMT
Content-Type: application/json; charset=utf-8
ETag: W/"2-vyGp6PvFo4RvsFtPoIWeCReyIC8"
Content-Range: bytes 0-1/2
Content-Length: 2

Backend dependencies:

"dependencies": {
    "bcryptjs": "^2.4.3",
    "body-parser": "^1.19.1",
    "cors": "^2.8.5",
    "ejs": "^3.1.6",
    "express": "^4.17.2",
    "gridfs-stream": "^1.1.1",
    "method-override": "^3.0.0",
    "mongodb": "^4.2.2",
    "mongoose": "^6.1.2",
    "multer": "^1.4.4",
    "multer-gridfs-storage": "^5.0.2"
  }

Frontend dependencies:

    "axios": "^0.24.0",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-player": "^2.9.0",
    "react-router-dom": "^6.2.1",
    "react-scripts": "4.0.3",
    "web-vitals": "^1.1.2"
Oyosied
  • 43
  • 7

1 Answers1

1

I managed to fix the issue. Note that GridFSBucket has a default bucket name. Going over the API docs it says it appends the bucket name ".files". My issues were that I did not define it and start end inside download stream were not defined correctly causing an error.

You may use it as well to stream Images,videos just change the content type on the frontend. Pretty generic stream.

exports.getVideoStream = (req, res, next) => {
  mongodb.MongoClient.connect(url, function (error, client) {
    if (error) {
      res.status(500).json(error);
      return;
    }

    // Check for range headers to find our start time
    const range = req.headers.range;
    if (!range) {
      res.status(400).send("Requires Range header");
    }

    const db = client.db('videos');
    // GridFS Collection
    console.log(req.params.id);
    db.collection('videos.files').findOne({_id:mongoose.Types.ObjectId(req.params.id)}, (err, video) => {
      if (!video) {
        res.status(404).send("No video uploaded!");
        return;
      }
      // Create response headers
      const videoSize = video.length;
      const start = Number(range.replace(/\D/g, ""));
      const end = videoSize - 1;

      const contentLength = end - start + 1;
      const headers = {
        "Content-Range": `bytes ${start}-${end}/${videoSize}`,
        "Accept-Ranges": "bytes",
        "Content-Length": contentLength,
        "Content-Type": "video/mp4",
      };

      // HTTP Status 206 for Partial Content
      res.writeHead(206, headers);

      // Get the bucket and download stream from GridFS
      const bucket = new mongodb.GridFSBucket(db,{bucketName:"videos"});
      
      const downloadStream = bucket.openDownloadStream(video._id, {
        start:start,
        end:end
      });

      // Finally pipe video to response
      console.log(streamCounter," start ",start," end ",end)
      streamCounter++;
      downloadStream.pipe(res);
    });
  });
  
};
Oyosied
  • 43
  • 7