首页 > 解决方案 > 如何发送/“流”视频文件到 HTML5 视频标签

问题描述

我试图将视频文件发送到 HTML5 <video>-tag 中。
我发现了一个可以追溯到 2010 年的片段,如果不是更早的话。
它在整个 Internet 上被复制并且仍在传播,但在代码风格、名称、使用的 Node.js API 版本或库方面存在一些细微差别。
我有一些问题。
这是片段:

app.get('/video', function(req, res) {
    const path     = 'some_path/video.mp4'
    const stat     = fs.statSync(path)
    const fileSize = stat.size
    const range    = req.headers.range
    if( range ) {
        const parts     = range.replace(/bytes=/, "").split("-")
        const start     = parseInt(parts[0],10);
        const end       = parts[1] ? parseInt(parts[1],10) : fileSize-1
        const chunksize = (end-start)+1
        const file      = fs.createReadStream(path, {start, end})
        const head = {
            'Content-Range' : `bytes ${start}-${end}/${fileSize}`,
            'Accept-Ranges' : 'bytes',
            'Content-Length': chunksize,
            'Content-Type'  : 'video/mp4',
        }
        res.writeHead(206, head)
        file.pipe(res)
    } else {
        const head = {
            'Content-Length': fileSize,
            'Content-Type'  : 'video/mp4',
        }
        res.writeHead(200, head)
        fs.createReadStream(path).pipe(res)
    }
})

这里只是它的几个来源:

  1. 通过 node.js 使用 HTML 5 流式传输视频
  2. 通过 fs 模块在 nodejs 中流式传输视频
  3. https://github.com/davidgatti/How-to-Stream-Movies-using-NodeJS/blob/master/routes/video.js
  4. https://github.com/tsukhu/rwd-spa-alljs-app/blob/master/routes/video_streamer.js
  5. https://tewarid.github.io/2011/04/25/stream-webm-file-to-chrome-using-node.js.html

很明显,这个片段还没有准备好生产:

  1. 它使用同步statSync()而不是异步版本,
  2. 它不解析Rangeheader的完整语法,也没有错误处理,
  3. 它是硬编码的,
  4. else早午餐可能是多余的,
  5. 等等

对于那件事我没有任何疑问。这是一个“入门级”代码。没关系。
但最重要的是它不能按预期工作......但仍然有效

据我所知<video>,浏览器会发送对带有标头的 -tag源的请求,Range其形式为

Range: bytes=12345-

并且,如果是初始请求,它将是

Range: bytes=0-

所以,这条线

const end = parts[1] ? parseInt(parts[1],10) : fileSize-1

等同于

const end = fileSize-1

而且,在初始请求中,服务器将发送的不是一小段视频,而是整个文件。并且在视频倒带的情况下 - 从请求的位置到结束的一个非常大的块。

如果您通过 Javascript 请求文件,这肯定不会按预期工作。您将等待文件完全加载,或者您需要以某种方式处理跟踪请求进度,这将导致相当复杂的客户端代码。
但它就像<video>-tag 的魅力一样,因为浏览器会代表您处理这个问题。

我们可以通过这样计算来解决这个end问题:

const preferred_chunksize = 10000000 // arbitrary selected value
let end = parts[1] ? parseInt(parts[1],10) : start + preferred_chunksize
if( end > fileSize-1 ) end = fileSize-1

或者,考虑到Range用于<video>-tag 的标头形式,即使这样:

const preferred_chunksize = 10000000 // arbitrary selected value
let end = start + preferred_chunksize
if( end > fileSize-1 ) end = fileSize-1

好的,现在它确实发送了预期大小的部分响应。但

  1. 这些行比
    const end = fileSize-1
    
  2. 我们需要preferred_chunksize明智地选择。例如,小块大小preferred_chunksize=1000会发出大量请求,并且工作速度会明显变慢。
    虽然,至少在 Chrome 和 Firefox 中,原始版本的代码流视频文件非常好:我没有看到过多的缓存或内存使用,或速度问题。而且我无需担心preferred_chunksize价值。

所以我的问题是:我是否应该费心发送正确大小的块(如果我只需要将视频发送到<video>-tag 中),是否有任何流行的浏览器/客户端 js 库无法播放与原始文件一起提供的视频文件片段?或者这个片段的方法是否有任何隐藏的问题?

标签: javascriptnode.jshttp-headersvideo-streaminghtml5-video

解决方案


服务器无法选择发送回客户端的字节范围。客户端发出请求,服务器必须满足它。发回其他任何内容都是错误的,浏览器可能不会播放视频

视频标签没有发出 XHR 请求,它在下载完成之前访问了响应数据(您也可以通过流 apis 访问)。Range: bytes=0-浏览器可以(并且经常这样做)通过关闭 TCP 会话来取消开放式范围请求( )。当它请求一个范围时,例如Range: bytes=12345-它是因为它已经解析了视频文件头,并且它知道它需要的数据位于偏移量 12345。


推荐阅读