node.jsvideo-streaminghtml5-videogridfsrange-header

html video player not able to play small videos from gridfs stream but able to play large videos


I am able to play large videos like 1GB , and 500MB but for video sizes like 60MB or lesser, the browser is not loading them if it receives content. for small videos not able to load even metadata

for large video- loaded

video html-


<video src="${url}" style="width:100% !important; height: inherit; background: black;" controls autoplay muted preload="metadata"></video>

I tried manipulating the responses content range , status but none are working.

My server code for response to range request-

route.get('/video/:id', async (req, res) => {
const { id } = req.params;`your text`
const range = req.headers.range;`your text`
    console.log(range);
    if (!range || range === undefined) {
    res.writeHead(416,{
        "Content-Range":`bytes */*`
    }).send("Requires Range header");
    return;
    }
    try {
        const cursor = await bucket.find({ "_id": id });
        let size, type;
        await cursor.forEach(document => {
        size = document.length;
        type = document.metadata.mimeType;
        });
        let CHUNK_SIZE = 1024 *1024 ; // 1MB
        let start = Number(range.replace(/\D/g, ""));
        const end = Math.min(start + CHUNK_SIZE, size-1);
        if(start>=size || start===end){
        res.writeHead(416,{
            "Content-Range":`bytes */${size}`
        })
        return res.end();
        }
        try {
        const downloadStream = await bucket.openDownloadStream(id, {
            start: start,
            end: end
        });
        const contentLength = end - start;
        const headers = {
            "Content-Range": `bytes ${start}-${end}/${size}`,
            "Accept-Ranges": "bytes",
            "Content-Length": contentLength,
            "Content-Type": "video/mp4",
        };
        res.writeHead(206, headers);
        downloadStream.pipe(res);
        } catch (err) {
        console.log('Error: ' + err);
        res.status(416).send();
        }
    } catch (err) {
        console.log('Error: ' + err);
        res.status(416).send();
    }
});

Solution

  • Issue Solved !!!
    Problem was occurring when the range request were like close to end to file or when this code minm condition hit for size -1

    const end = Math.min(start + CHUNK_SIZE, **size-1**);
    

    The problem here was the stream size:

    const downloadStream = await bucket.openDownloadStream(id, {
      start: start,
      **end: end**
    });
    

    Stream was never reaching its end

    So if we configure the end to actual end this solve the problem

    Final Code -

    route.get('/video/:id', async (req, res) => {
      const { id } = req.params;
      const range = req.headers.range;
      try {
        const cursor = await bucket.find({ "_id": id }).toArray();
        const document = cursor[0];
        const size = document.length;
        const type = document.metadata.mimeType;
        if (range) {
          let CHUNK_SIZE = 5 * 1024 * 1024; // 5MB
          var parts = range.replace(/bytes=/, "").split("-");
          var partialstart = parts[0];
          var partialend = parts[1];
          var start = parseInt(partialstart, 10);
          var end = partialend ? parseInt(partialend, 10) : Math.min(start + CHUNK_SIZE, size - 1);
          if (start >= size) {
            res.writeHead(416, {
              "Content-Range": `bytes */${size}`
            })
            return res.end();
          }
          try {
    
            const downloadStream = await bucket.openDownloadStream(id, {
              start: start,
              end: end == size - 1 ? size : end
            });
            const contentLength = end - start + 1;
            const headers = {
              "Content-Range": `bytes ${start}-${end}/${size}`,
              "Accept-Ranges": "bytes",
              "Content-Length": `${contentLength}`,
              "Content-Type": type,
            };
            res.writeHead(206, headers);
            console.info('Satisfied Range: ' + `${start}-${end}/${size}`);
    
            downloadStream.pipe(res);
          } catch (err) {
            console.log('Error: ' + err);
            res.status(416).send();
          }
        } else {
          const downloadStream = await bucket.openDownloadStream(id);
          res.setHeader('Cache-Control', 'private, max-age=0, must-revalidate');
          res.setHeader('Content-Length', size.toString());
          res.setHeader('Content-Type', type);
          res.writeHead(200);
          res.end(downloadStream);
        }
      } catch (err) {
        console.log('Error: ' + err);
        res.status(416).send();
      }
    });