之前在小组内做完HLS的分享之后,正好有一个视频相关的线上demo,视频格式是MP4,大概18M,接口只能一次将整个视频资源获取下来之后才能播放,这样就会有一个空白等待期,也不支持播放进度拖拽跳转,同组的同学就让我对这个演示视频进行优化。
查阅了一些资料,也尝试了一些视频播放相关的框架之后,接口不支持流媒体,前端还是需要等待整个资源下载下来之后,才能做进度拖拽,跳转播放等功能,于是我的方案就是在node层拦截视频请求接口,利用文件流实现流媒体。
ps:有同学给我说,不需要在node转发,也可以实现,暂时还没有找到相关方案。
HTTP的断点续传
文件上传下载时,记录上一次上传下载的位置,再从标记位置继续传输,或者多线程下载,根据标记可按需获取。
在以前版本的 HTTP 协议是不支持断点的,HTTP/1.1 开始就支持了。一般断点下载时才用到 Range 和 Content-Range 实体头。
Range
请求资源的部分内容(不包括响应头的大小),单位是byte,即字节,从0开始,如果服务器能够正常响应的话,服务器会返回 206 Partial Content 的状态码及说明,如果不能处理这种Range的话,就会返回整个资源以及响应状态码为 200 OK,(这个要注意,要分段下载时,要先判断这个)用于请求头中,指定第一个字节的位置和最后一个字节的位置,一般格式:
Range: bytes=start-end
eg:
Range: bytes=10- :第10个字节及最后个字节的数据
Range: bytes=40-100 :第40个字节到第100个字节之间的数据
响应头Content-Range
Content-Range: bytes 0-100/5000
这个表示相应了0到100个字节的数据,共有5000个字节大小
Content-Length: 5000
总文件大小
node中fs模块
fs.statSync(path, callback);
该方法用于检测文件的状态,可以借此来判断某个文件是否存在。path参数传入该文件的绝对物理路径,callback回调函数有两个参数err和stats。其中err为错误信息参数,stats为一个文件状态对象。
方法返回一个数组
Stats {
dev: 2114,
ino: 48064969,
mode: 33188,
nlink: 1,
uid: 85,
gid: 100,
rdev: 0,
size: 527,
blksize: 4096,
blocks: 8,
atimeMs: 1318289051000.1,
mtimeMs: 1318289051000.1,
ctimeMs: 1318289051000.1,
birthtimeMs: 1318289051000.1,
atime: Mon, 10 Oct 2011 23:24:11 GMT,
mtime: Mon, 10 Oct 2011 23:24:11 GMT,
ctime: Mon, 10 Oct 2011 23:24:11 GMT,
birthtime: Mon, 10 Oct 2011 23:24:11 GMT
}
fs.createReadStream(path, [options])创建文件可读流,options可以指定读取文件参数,包括start,end,指定读取特定位置文件数据
完整代码
const express = require('express');
const path = require('path');
const app = express();
const fs = require('fs');
app.get('/*.mp4$/', function(req, res) {
let path = 'video.mp4';
let stat = fs.statSync(path);
let fileSize = stat.size;
let range = req.headers.range;
if(range) {
//有range头才使用206状态码
let parts = range.replace(/bytes=/, "").split("-");
let start = parseInt(parts[0], 10);
let end = parts[1] ? parseInt(parts[1], 10) : start + 999999;
// end 在最后取值为 fileSize - 1
end = end > fileSize - 1 ? fileSize - 1 : end;
let chunksize = (end - start) + 1;
let file = fs.createReadStream(path, {start, end});
let 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 {
let head = {
'Content-Length': fileSize,
'Content-Type': 'video/mp4',
};
res.writeHead(200, head);
fs.createReadStream(path).pipe(res);
}
});
app.use(express.static(path.join(__dirname, 'output')));
var server = app.listen(3000, function() {
var host = server.address().address;
var port = server.address().port;
console.log('Example app listening at http://%s:%s', host, port);
});