用 Node.js 和 HTML5 进行视频推流

1_eu1SkzhIVtLKgGB5vvG7Jg

有人曾问过我怎么用 Node.js 进行视频推流。我之前没试过,所以这次决定尝试一下!我想通过本文与大家分享一下我的使用体验。

Node.js 进行视频推流的难点在于要创建一个路径,通过该路径将一个 mp4 文件传输到页面上,并确保视频可供观看。

以下是我的分解步骤:

  • 创建服务器路径以发送视频。

  • 用 HTML5 和 JS 请求 feed(视频源)。

  • 分批加载视频,不是从头开始加载。

点击这里查看视频推流的工作演示。

推流

我们要通过推流播放视频,所以不能把所有内容放进一个包里发到前端,而是应该一次只发一部分。

因为是通过推流发送视频,所以观看视频不需要等页面从服务器下载整个视频,可以先向服务器请求前几秒的视频,然后一边播放视频一边下载视频的剩余部分。

这个方法还适用于大篇幅文本的发送,用户可以直接开始阅读,无需等待整篇文章下载完成。

相关理论

  • 获取文件大小 :Node 中的 fs 有一个名为 statSync 的方法,该方法会返回文件的统计信息。在这些统计数据中,我们需要知道的是当前加载的 chunk 到达文件末端时的尺寸。我用 statSync 是为了避免同步,同时让新手更容易理解代码,大家也可以用 stat

  • 从文件中创建推流fs 包含另一个名为 createReadStream 的方法,该方法将在给定文件(开始和结束语块)的条件创建推流。

const fileChunk = fs.createReadStream(sample.mp4, {start, end});
  • 语块的大小: 请求会提供起始语块。为了搞清楚要加载多少文件,我用结束语块的大小(如果不可用,使用完整的文件大小)减去起始块的大小:
endChunk - startChunk
  • HTTP 206: HTTP 206 用于部分内容,也是我们想要的 connection 标头。我们持续地为前端提供语块,希望在发出请求时可以使用起始语块。至少要定义下列内容:
'Content-Range': 'bytes chunkStart-chunkEnd/chunkSize'
'Accept-Ranges': 'bytes'
'Content-Length': chunkSize
'Content-Type': 'video/mp4'

服务器

在以上内容的基础上, 我最终在我的路由中得到了类似内容,将其命名为 video (我正在用 Express 创建路由)。

app.get('/video', function(req, res) {
  const path = 'assets/sample.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)
  }
});

虽然代码有点多,但不要担心,大家可以随时通过 demo 进行调试。

以下是所有流程:

  • 提出请求后,获取文件的大小,并在 else 语句中发送视频的前几个语块。

  • 开始观看视频后(通过 localhost:3000/video 或前端来访问路由),会发出后续请求,这次在标头注明范围,方便获取下一个语块的起点。

  • 再次读取文件来创建另一个推流,传递开始和结束的新值(很有可能是请求标头中的当前部分以及视频的文件大小)。

  • 通过应用前面的公式,将 206 标头响应设置为仅发送部分新生成的推流。

前端

使用 HTML5 video 标签,前端设置非常简单——只添加源路由即可。

<video id="videoPlayer" controls>
  <source src="http://localhost:3000/video" type="video/mp4">
</video>

我们可以通过 controls 属性查看播放器的控件。

1_bchK19b3xAbDaJegZIOe_w

如果没有 controls 属性,可以通过访问播放器组件,自行编写软件和其他特性。这种情况下,id 是 videoPlayer

因此,如果你的 HTML 上有一个按钮,就可以通过以下操作来复制播放(play)/停止(stop)按钮:

const VP = document.getElementById('videoPlayer') // player
const VPToggle = document.getElementById('toggleButton') // button
VPToggle.addEventListener('click', function() {
  if (VP.paused) VP.play()
  else VP.pause()
})

开发者工具里的 network 选项卡中可以看到分块推流,尤其是在节流连接的情况下。


一个 30 MB 文件的推流的部分示例

结束语

我没想到这个简单的操作运行起来这么顺利。当然,实施中也有一些缺陷,比如起始语块有时候会跟预想的不一样(可能是因为挂着的连接,我在此示例中没有进行处理)。

无论如何,这对于刚开始创建推流应用程序的人来说是一个很好的起点。如果你有更好或不容易出错的方法,请告诉我!

原文作者:Diogo Spínola
原文链接:88https://medium.com/better-programming/video-stream-with-node-js-and-html5-320b3191a6b6

注册登录 后评论
    // 作者
    声网技术社区 发布于 声网开发者社区
    • 0
    // 相关帖子
    Coming soon...
    • 0
    用 Node.js 和 HTML5 进行视频推流声网技术社区 发布于 声网开发者社区