如果需要下载视频,可以通过 xhr 或者是 fetch 来下载,xhr 自带了 api 来监听下载的进度, fetch 也可以使用流式的接口来监听下载进度。
但是这两种方式都有一个问题,就是下载的内容会先保存在内存中,然后再触发下载来保存在磁盘中,这样一来效率比较低,而且文件如果很大那根本就无法实现。
如果是使用传统的下载方式,那么下载进度条只能在浏览器的下载管理中查看。
那怎么可以做到既不先把数据保存在内存中,也能在页面上显示下载进度
如果需要下载视频,可以通过 xhr 或者是 fetch 来下载,xhr 自带了 api 来监听下载的进度, fetch 也可以使用流式的接口来监听下载进度。
但是这两种方式都有一个问题,就是下载的内容会先保存在内存中,然后再触发下载来保存在磁盘中,这样一来效率比较低,而且文件如果很大那根本就无法实现。
如果是使用传统的下载方式,那么下载进度条只能在浏览器的下载管理中查看。
那怎么可以做到既不先把数据保存在内存中,也能在页面上显示下载进度
### 回答
要在 web 端监听下载进度百分比而不先将数据保存在内存中,你可以使用 Service Workers 结合 `fetch` API 的流式下载。Service Workers 在后台运行脚本,独立于网页,能够处理网络请求,包括下载文件。
#### 步骤概述
1. **注册 Service Worker**:
在你的网页中注册一个 Service Worker,它将处理下载请求。
2. **在 Service Worker 中处理下载**:
使用 `fetch` API 发起下载请求,并通过 `ReadableStream` 接口处理响应数据。同时,你可以通过 `postMessage` 方法将下载进度信息发送回主线程。
3. **在主线程中更新进度**:
监听来自 Service Worker 的消息,并更新页面上的下载进度条。
#### 示例代码
**主线程(HTML + JavaScript)**:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Download Progress</title>
</head>
<body>
<button id="downloadBtn">Download Video</button>
<progress id="progressBar" value="0" max="100" style="width: 100%;"></progress>
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js').then(function(registration) {
console.log('Service Worker registered with scope:', registration.scope);
}).catch(function(error) {
console.log('Service Worker registration failed:', error);
});
}
document.getElementById('downloadBtn').addEventListener('click', function() {
navigator.serviceWorker.controller.postMessage({ action: 'download', url: 'path/to/video.mp4' });
});
navigator.serviceWorker.controller.addEventListener('message', function(event) {
if (event.data.action === 'progress') {
document.getElementById('progressBar').value = event.data.progress;
} else if (event.data.action === 'done') {
alert('Download complete!');
}
});
</script>
</body>
</html>
**Service Worker(service-worker.js)**:
self.addEventListener('message', function(event) {
if (event.data.action === 'download') {
const url = event.data.url;
const responseType = 'blob';
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.body.getReader();
})
.then(reader => {
const totalSize = response.headers.get('Content-Length') || 1; // Fallback to 1 to avoid division by zero
let loaded = 0;
function read() {
reader.read().then(({ done, value }) => {
if (done) {
self.postMessage({ action: 'done' });
return;
}
loaded += value.byteLength;
const progress = Math.round((loaded / totalSize) * 100);
self.postMessage({ action: 'progress', progress });
read(); // Recursive call to read the next chunk
}).catch(error => {
console.error('Error reading stream:', error);
});
}
read();
})
.catch(error => {
console.error('Error downloading file:', error);
});
}
});
#### 说明
- **Service Worker 注册**:在网页中注册 Service Worker,以便它可以处理后台任务。
- **消息传递**:通过 `postMessage` 方法在 Service Worker 和主线程之间传递消息,包括下载请求和进度更新。
- **流式下载**:使用 `fetch` API 和 `ReadableStream` 接口进行流式下载,避免将整个文件加载到内存中。
- **进度更新**:在 Service Worker 中读取文件流时,计算并发送下载进度到主线程,以便更新页面上的进度条。
这种方法允许你在不将下载内容首先保存在内存中的情况下,在页面上显示下载进度。
13 回答13k 阅读
8 回答2.7k 阅读
2 回答5.2k 阅读✓ 已解决
7 回答2.1k 阅读
5 回答1.3k 阅读
3 回答2.3k 阅读✓ 已解决
3 回答1.3k 阅读✓ 已解决
使用分片下载结合Service Worker
主线程 (index.js)
负责启动下载过程,处理从Service Worker接收的消息,并更新下载进度或处理下载完成的逻辑。
Service Worker (sw.js)
负责分片下载文件,记录已下载的进度,并将下载进度和完成消息传回主线程。
HTML
负责显示下载进度,并包含主线程的JavaScript代码。
功能逻辑说明
主线程 (index.js):
Service Worker (sw.js):
HTML: