javascript - 如何发送/“流”视频文件到 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)
}
})
这里只是它的几个来源:
- 通过 node.js 使用 HTML 5 流式传输视频
- 通过 fs 模块在 nodejs 中流式传输视频
- https://github.com/davidgatti/How-to-Stream-Movies-using-NodeJS/blob/master/routes/video.js
- https://github.com/tsukhu/rwd-spa-alljs-app/blob/master/routes/video_streamer.js
- https://tewarid.github.io/2011/04/25/stream-webm-file-to-chrome-using-node.js.html
很明显,这个片段还没有准备好生产:
- 它使用同步
statSync()
而不是异步版本, - 它不解析
Range
header的完整语法,也没有错误处理, - 它是硬编码的,
else
早午餐可能是多余的,- 等等
对于那件事我没有任何疑问。这是一个“入门级”代码。没关系。
但最重要的是它不能按预期工作......但仍然有效。
据我所知<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
好的,现在它确实发送了预期大小的部分响应。但
- 这些行比
const end = fileSize-1
- 我们需要
preferred_chunksize
明智地选择。例如,小块大小preferred_chunksize=1000
会发出大量请求,并且工作速度会明显变慢。
虽然,至少在 Chrome 和 Firefox 中,原始版本的代码流视频文件非常好:我没有看到过多的缓存或内存使用,或速度问题。而且我无需担心preferred_chunksize
价值。
所以我的问题是:我是否应该费心发送正确大小的块(如果我只需要将视频发送到<video>
-tag 中),是否有任何流行的浏览器/客户端 js 库无法播放与原始文件一起提供的视频文件片段?或者这个片段的方法是否有任何隐藏的问题?
解决方案
服务器无法选择发送回客户端的字节范围。客户端发出请求,服务器必须满足它。发回其他任何内容都是错误的,浏览器可能不会播放视频
视频标签没有发出 XHR 请求,它在下载完成之前访问了响应数据(您也可以通过流 apis 访问)。Range: bytes=0-
浏览器可以(并且经常这样做)通过关闭 TCP 会话来取消开放式范围请求( )。当它请求一个范围时,例如Range: bytes=12345-
它是因为它已经解析了视频文件头,并且它知道它需要的数据位于偏移量 12345。
推荐阅读
- c# - 从 Elastic 脚本参数中循环遍历数组
- php - Laravel,使用参数将未注册用户重定向到登录路由
- r - 由反应数据制成的用户交互表
- javascript - cypress:在几个函数中使用一个变量
- mongodb - 具有键值数组计数的 Mongodb 嵌套数组组
- javascript - 延迟加载在 Angular 8 和 webpack 中不起作用
- reactjs - 是否绝对需要将 useEffect 中的所有变量列为依赖项?
- python - Python:嵌套字典 - 如果键不存在则创建,否则总和 1
- c# - 从 FTP 服务器下载文件:远程服务器返回错误:(425) 无法打开数据连接
- reactjs - ThemeProvider 的 Jest Manual Mock