Node.js / Express视频流(HTTP 206部分内容)

时间:2021-07-14 15:26:33

I have a binary document (mp4 video file) in a database (MarkLogic). I am using the database's Node.js API to stream the document in chunks. The setup looks like this:

我在数据库(MarkLogic)中有一个二进制文件(mp4视频文件)。我正在使用数据库的Node.js API以块的形式传输文档。设置如下所示:

html file

<video controls="controls" width="600">
  <source src="/video/myvideo.mp4" type="video/mp4">
</video>

In express I have then setup a route which handles the /video/:param route (in the database the video has the unique identifier which is the string '/video/myvideo.mp4')

然后在Express中我设置了一个处理/ video /:param路由的路由(在数据库中,视频有唯一的标识符,即字符串'/video/myvideo.mp4')

node.js

// I'm only showing the relevant things in here

const serveVideo = (req, res) => {
  var stream = db.documents.read('/gopro/malta.mp4').stream('chunked');

  var chunks = [];
  var chunkBytes = 0;
  var start = 0;
  stream.on('data', (chunk) => {
    var headers;
    var range = req.headers.range;
    var total = 214335483; //total length of vid in bytes

    if (range) {
      var chunkSize = chunk.length;
      // (start === 0) ? start = 0 : start += chunkBytes;
      if (chunkBytes === 0) {
        start = 0
      } else {
        start = chunkBytes + 1
      }
      chunkBytes += chunkSize;

      headers = {
        'Content-Range': 'bytes ' + start + '-' + chunkBytes + '/' + total,
        'Accept-Ranges': 'bytes',
        'Content-Length': chunkSize,
        'Content-Type': 'video/mp4'
      };
      res.writeHead(206, headers);
      chunks.push(chunk);
    }
  });
  stream.on('end', () => {
    var allChunks = Buffer.concat(chunks);
    res.end(allChunks);
  });
});


router.route('/video/:uri').get(serveVideo);

Now of course the above fails with 'Error: Can't set headers after they are sent.' which is all fair and square. But I can't get my head around this - the .stream('chunked') call forces the database to retrieve the document in chunks and I do see those chunks just fine, however how can I return a 206 for the browser? I can't do it in the .on('data') as data is being streamed so the header would be sent multiple times. I guess which database I'm using is not really relevant - I would like to understand the concept, or at least see what I'm doing wrong.

当然,上面的错误是“错误:发送后无法设置标头”。这是公平的。但我无法理解这一点 - .stream('chunked')调用强制数据库以块的形式检索文档,我确实看到那些块很好,但是如何为浏览器返回206呢?我无法在.on('data')中执行此操作,因为数据正在流式传输,因此标头将被多次发送。我想我正在使用的数据库并不是真正相关的 - 我想理解这个概念,或者至少看看我做错了什么。

Any help is appreciated. All the examples and other discussions that I have seen that stream video using Node.js are reading the video file from disk.

任何帮助表示赞赏。我看到的所有使用Node.js流视频的示例和其他讨论都是从磁盘读取视频文件。

update

Making a change to the code now allows FF to play the video but not Chrome:

现在更改代码允许FF播放视频而不是Chrome:

let stream = db.documents.read({uris:'/gopro/malta.mp4'}).stream('chunked');
stream.pipe(res);

There are no errors in Chrome's console. Here are the header details - note that there are two requests for the mp4 file:

Chrome控制台中没有错误。以下是标题详细信息 - 请注意,mp4文件有两个请求:

1st

Response Headers
Connection:keep-alive
Date:Sat, 21 May 2016 17:05:30 GMT
Transfer-Encoding:chunked
X-Powered-By:Express

Request Headers
view source
Accept:*/*
Accept-Encoding:identity;q=1, *;q=0
Accept-Language:en-US,en;q=0.8,hu;q=0.6,ro;q=0.4,it;q=0.2
Cache-Control:no-cache
Connection:keep-alive
Cookie:__distillery=v20150227_a8e22306-65b3-4c2e-9a8a-159e308156ad; __smToken=7nYU8NYQY15mPowjjCZsS5D3
DNT:1
Host:localhost:8080
Pragma:no-cache
Range:bytes=0-
Referer:http://localhost:8080/
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36

2nd

Response Headers
Connection:keep-alive
Date:Sat, 21 May 2016 17:05:31 GMT
Transfer-Encoding:chunked
X-Powered-By:Express

Request Headers
view source
Accept:*/*
Accept-Encoding:identity;q=1, *;q=0
Accept-Language:en-US,en;q=0.8,hu;q=0.6,ro;q=0.4,it;q=0.2
Cache-Control:no-cache
Connection:keep-alive
Cookie:__distillery=v20150227_a8e22306-65b3-4c2e-9a8a-159e308156ad; __smToken=7nYU8NYQY15mPowjjCZsS5D3
DNT:1
Host:localhost:8080
Pragma:no-cache
Range:bytes=28-
Referer:http://localhost:8080/
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36

2 个解决方案

#1


4  

The on('data') callback could always have a closure over an isFirstChunk variable that is initialized to true and have a test that, if isFirstChunk is true, emits the headers and sets isFirstChunk to false.

on('data')回调可以始终对isFirstChunk变量进行闭包,该变量初始化为true,并且如果isFirstChunk为true,则测试将发出标头并将isFirstChunk设置为false。

It would be preferable to pipe the stream if possible. npm might offer a stream library (maybe even through2()?) that has an event when the first data arrives.

如果可能的话,最好管道流。 npm可能会提供一个流库(甚至可能是to2()?),它在第一个数据到达时有一个事件。

For the longer term, you could reasonably file an RFE for an event at the start of a stream.

从长远来看,您可以合理地在流的开头为事件提交RFE。

Hoping that helps,

希望有所帮助,

#2


4  

You most likely don't need all the chunk stuff. Set headers manually:

你很可能不需要所有大块的东西。手动设置标题:

res.status(206);

Then simply pipe the response:

然后只需管道响应:

let stream = db.yourChunkStuff();
stream.pipe(res);

The simplest way to is pipe streams.

最简单的方法是管道流。

#1


4  

The on('data') callback could always have a closure over an isFirstChunk variable that is initialized to true and have a test that, if isFirstChunk is true, emits the headers and sets isFirstChunk to false.

on('data')回调可以始终对isFirstChunk变量进行闭包,该变量初始化为true,并且如果isFirstChunk为true,则测试将发出标头并将isFirstChunk设置为false。

It would be preferable to pipe the stream if possible. npm might offer a stream library (maybe even through2()?) that has an event when the first data arrives.

如果可能的话,最好管道流。 npm可能会提供一个流库(甚至可能是to2()?),它在第一个数据到达时有一个事件。

For the longer term, you could reasonably file an RFE for an event at the start of a stream.

从长远来看,您可以合理地在流的开头为事件提交RFE。

Hoping that helps,

希望有所帮助,

#2


4  

You most likely don't need all the chunk stuff. Set headers manually:

你很可能不需要所有大块的东西。手动设置标题:

res.status(206);

Then simply pipe the response:

然后只需管道响应:

let stream = db.yourChunkStuff();
stream.pipe(res);

The simplest way to is pipe streams.

最简单的方法是管道流。