当前位置: 首页 > 知识库问答 >
问题:

javascript - web 端如何监听下载进度百分比?

白迪
2024-12-03

如果需要下载视频,可以通过 xhr 或者是 fetch 来下载,xhr 自带了 api 来监听下载的进度, fetch 也可以使用流式的接口来监听下载进度。

但是这两种方式都有一个问题,就是下载的内容会先保存在内存中,然后再触发下载来保存在磁盘中,这样一来效率比较低,而且文件如果很大那根本就无法实现。

如果是使用传统的下载方式,那么下载进度条只能在浏览器的下载管理中查看。

那怎么可以做到既不先把数据保存在内存中,也能在页面上显示下载进度

共有2个答案

姜博
2024-12-03

使用分片下载结合Service Worker

主线程 (index.js)

负责启动下载过程,处理从Service Worker接收的消息,并更新下载进度或处理下载完成的逻辑。

// 记录已下载的字节数,用于断点续传
let downloadedSize = 0;

// 开始下载函数
function startDownload(url) {
    // 确保 Service Worker 已经注册并准备好
    navigator.serviceWorker.ready.then(registration => {
        // 向 Service Worker 发送消息,包含下载 URL 和分片大小
        registration.active.postMessage({ url, chunkSize: 2 * 1024 * 1024, downloadedSize });
    });
}

// 监听来自 Service Worker 的消息
navigator.serviceWorker.addEventListener('message', event => {
    if (event.data.type === 'progress') {
        // 更新下载进度
        console.log(`Download progress: ${event.data.progress.toFixed(2)}%`);
        document.getElementById('progress').innerText = `Download progress: ${event.data.progress.toFixed(2)}%`;
    } else if (event.data.type === 'complete') {
        // 下载完成,创建下载链接并触发下载
        const link = document.createElement('a');
        link.href = event.data.url;
        link.download = 'video.mp4';
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
    } else if (event.data.type === 'resume') {
        // 更新已下载的字节数,用于断点续传
        downloadedSize = event.data.downloadedSize;
    }
});

// 开始下载
startDownload('video_url');

Service Worker (sw.js)

负责分片下载文件,记录已下载的进度,并将下载进度和完成消息传回主线程。

// 监听来自主线程的消息
self.addEventListener('message', async (event) => {
    const { url, chunkSize, downloadedSize } = event.data;
    // 获取文件总大小
    const response = await fetch(url, { method: 'HEAD' });
    const totalSize = parseInt(response.headers.get('Content-Length'), 10);
    let currentDownloadedSize = downloadedSize;
    const chunks = [];

    // 分片下载文件
    for (let start = downloadedSize; start < totalSize; start += chunkSize) {
        const end = Math.min(start + chunkSize - 1, totalSize - 1);
        const chunk = await fetch(url, {
            headers: { 'Range': `bytes=${start}-${end}` }
        }).then(res => res.blob());
        chunks.push(chunk);
        currentDownloadedSize += chunk.size;

        // 向主线程发送下载进度
        self.clients.matchAll().then(clients => {
            clients.forEach(client => client.postMessage({
                type: 'progress',
                progress: (currentDownloadedSize / totalSize) * 100,
                downloadedSize: currentDownloadedSize
            }));
        });
    }

    // 合并所有分片并创建 Blob
    const blob = new Blob(chunks);
    const link = self.registration.scope + 'video.mp4';
    const cache = await caches.open('video-cache');
    await cache.put(link, new Response(blob));

    // 向主线程发送下载完成消息
    self.clients.matchAll().then(clients => {
        clients.forEach(client => client.postMessage({
            type: 'complete',
            url: link
        }));
    });
});

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>
    <!-- 显示下载进度 -->
    <div id="progress">Download progress: 0%</div>
    <script src="index.js"></script>
</body>
</html>

功能逻辑说明

  1. 主线程 (index.js)

    • 记录已下载的字节数,用于断点续传。
    • 注册并准备Service Worker。
    • 向Service Worker发送下载请求,包含下载URL和分片大小。
    • 监听来自Service Worker的消息,更新下载进度或处理下载完成。
  2. Service Worker (sw.js)

    • 监听来自主线程的消息,获取下载URL和分片大小。
    • 获取文件总大小,并初始化已下载字节数。
    • 分片下载文件,每次下载一个小块,并记录已下载的字节数。
    • 向主线程发送下载进度消息。
    • 合并所有分片并创建Blob,缓存文件。
    • 向主线程发送下载完成消息。
  3. HTML

    • 显示下载进度。
湛铭
2024-12-03
### 回答

要在 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 中读取文件流时,计算并发送下载进度到主线程,以便更新页面上的进度条。

这种方法允许你在不将下载内容首先保存在内存中的情况下,在页面上显示下载进度。
 类似资料:
  • 本文向大家介绍Android实现百分比下载进度条效果,包括了Android实现百分比下载进度条效果的使用技巧和注意事项,需要的朋友参考一下 现在很多APP中都会集成下载功能,所以有一个方便好看又实用的进度条来展示下载进度很有必要,也能提高用户体验,在这里我就把项目里的下载进度条抽取出来分享给大家,话不多说,先看效果图: 这个进度条是自定义的一个View,其中有一个自定义属性就是百分比文字的大小(也

  • 本文向大家介绍使用Retrofit下载文件并实现进度监听的示例,包括了使用Retrofit下载文件并实现进度监听的示例的使用技巧和注意事项,需要的朋友参考一下 1.前言 最近要做一个带进度条下载文件的功能,网上看了一圈,发现好多都是基于 OkHttpClient 添加拦截器来实现的,个人觉得略显复杂,所以还是采用最简单的方法来实现:基于文件写入来进行进度的监听。 2.实现步骤 2.1 设计监听接口

  • 本文向大家介绍android中DownloadManager实现版本更新,监听下载进度实例,包括了android中DownloadManager实现版本更新,监听下载进度实例的使用技巧和注意事项,需要的朋友参考一下 DownloadManager简介 DownloadManager是Android 2.3(API level 9)用系统服务(Service)的方式提供了DownloadManage

  • 我试图在请求中添加客户端进度条。理想的做法是获取请求头大小并接收 我对了解不多,但据我所知,必须启动请求才能实现这一点(请参见此处) PS1.虽然主要的范围为这,是一个文件上传进度条,我希望能够申请的全部请求大小。PS2.我知道有几个图书馆免费提供这一点,但ATM我不想使用任何。

  • 问题内容: 如何使用进度条显示页面的加载百分比?…(类似于它们在Flash中的显示方式) 谢谢 问题答案: 不可能(在IE8和FF3和Opera上,没有插件或扩展名)。如果要实际加载百分比,请包括HTML + Javascript +样式表+图片。您只能检测到页面中加载了多少文件(此技术只能检测到图像和javascript)。

  • 本文向大家介绍如何用Node监听80端口?相关面试题,主要包含被问及如何用Node监听80端口?时的应答技巧和注意事项,需要的朋友参考一下 这题有陷阱!在类Unix系统中你不应该去监听80端口,因为这需要超级用户权限。因此不推荐让你的应用直接监听这个端口。 目前,如果你一定要让你的应用80端口的话,你可以有通过在Node应用的前方再添加一层反向代理(例如nginx)来实现,如下图。否则,建议你直接

  • 我有一个文件 这个类是一个独立的文件, 这个类被多个其他文件所引用 如何提前hack这个类 让他每次实例化的时候回调给我, 让我知道他实例化了从而运行一些实例化的代码

  • fetch 方法允许去跟踪 下载 进度。 请注意:到目前为止,fetch 方法无法跟踪 上传 进度。对于这个目的,请使用 XMLHttpRequest,我们在后面章节会讲到。 要跟踪下载进度,我们可以使用 response.body 属性。它是 ReadableStream —— 一个特殊的对象,它可以逐块(chunk)提供 body。在 Streams API 规范中有对 ReadableStr