最近在做视频相关的项目,上次介绍了 视频的点播,今天来介绍一下视频的切片下载
通常是针对大资源,一次性请求一整个资源会导致响应时间过长,所以进行分批请求,可利用浏览器并发请求的能力
1、前置知识
(注意:应该是Content-Range)
在HTTP``request Header
中,有一个请求头字段Range
,用来表示范围请求
- 即只请求目标文件的一部分
javaScript
GET /videoFile.mp4 HTTP/1.1
Host: www.example.re
Range: bytes=0-9999
1.1 正常响应
当后端正常响应时,它对应的状态码为206
--- partial content
,表示部分内容;并且会在响应头添加Content-Length
字段,并返回请求的范围和资源长度
如下:
javaScript
HTTP/1.1 206 Partial Content
Content-Length: 10000
Content-Range: bytes 0-9999/25000
Accept-Ranges: bytes
1.2 预请求
客户端可以在发起范围请求之前,先发送一个head
请求,看服务端是否支持range
请求
- 若支持,服务端会在响应头中返回一个字段
Accept-Range: bytes
- 如不支持,则返回
Accept-Range: none
1.3 超出范围
当请求头中的范围超出资源大小时,服务端端则会返回416
--- Range Not Satisfiable
2、后端
后端仍然使用koa
来完成,在之前项目的基础上增加两个接口:
head
请求获取文件的大小get
进行范围请求
2.1 获取文件大小
head
请求基本与get
请求相同(安全且幂等 ),只不过它没有响应体,只有响应头,一般用于请求元信息
这里将用于获取文件的大小,请求结果放在头部的Content-Length
字段
javaScript
router.head('/file/size', async (ctx) => {
const { originPath } = ctx.query
const filePath = path.join(__dirname, '../uploadFiles', originPath)
// 读取文件信息
const { size } = fs.statSync(filePath)
ctx.set('Content-Length', size)
ctx.status = 200
})
注意: 无需返回响应体信息,即使返回,客户端也不会接收到
2.2 进行范围请求
进行范围请求首先会先检查请求头是否有range
字段,如果没有,仍然返回一整个文件
javaScript
/**
* 下载文件
*/
router.get('/file/download', async (ctx) => {
const { originPath } = ctx.query
const filePath = path.join(__dirname, '../uploadFiles', originPath)
const { size } = fs.statSync(filePath)
const range = ctx.headers.range
if (!range) {
// 告诉客户端 服务端事支持range请求的
ctx.set('Accept-Range', 'bytes')
// 流式返回
ctx.body = fs.createReadStream(filePath);
return
}
})
createReadStream
接收第二个配置参数,可用于配置读取的起始、终止位置
javaScript
router.get('/file/download', async (ctx) => {
...
const range = ctx.headers.range // 其结构为:bytes=0-999
const [start, end] = gerRange(range)
// 表示超出范围
if (start > size || end > size) {
ctx.status = 416
ctx.body = ''
return
}
ctx.set('Accept-Ranges', 'bytes')
ctx.set('Content-Length', end - start + 1)
ctx.set('Content-Range', `bytes ${start}-${end}/${size}`)
ctx.status = 206
ctx.body = fs.createReadStream(filePath, {start, end})
})
3、前端
步骤:
head
请求获取文件大小- 请求
range
文件的blob
- 下载文件
javaScript
const downloadFile = async (path: string) => {
try {
const size = await fetchFileSize(path);
if (!size) return alert('下载失败');
const sizeQueue = getFileSlice(+size.headers['content-length']);
const peomiseQueue = sizeQueue.map(item => {
const [start, end] = item;
return fetchFileBlob(path, start, end);
});
Promise.all(peomiseQueue)
.then(res => {
const blob = new Blob(res, { type: 'video/mp4' });
const href = URL.createObjectURL(blob);
// 下载
const a = document.createElement('a');
a.download = path.split('/').pop() as string;
a.href = href;
a.click();
URL.revokeObjectURL(blob);
})
} catch (error) {
console.error(error);
}
};
下载一个大小为130+M的MP4文件,时间大概为1.2s
详细代码已上传github
参考: