进展:fetch blob JavaScript

3

我正在尝试使用fetch方法获取视频文件,我可以成功地下载文件并获得blob URL,但无法在下载过程中获取进度。

我尝试了以下方法:

    let response = await fetch('test.mp4');
    
    const reader = response.body.getReader();
    
    const contentLength=response.headers.get('Content-Length');
    
    let receivedLength = 0; 
    d=document.getElementById('progress_bar');
    while(true) 
    {
        const {done, value} = await reader.read();
        
        if (done) 
        {
            break;
        }
        receivedLength += value.length;
        d.innerHTML="Bytes loaded:"+receivedLength;
    }
    
    const blob = await response.blob();
    
    var vid=URL.createObjectURL(blob);

问题是我收到了"Response.blob: Body has already been consumed"的错误提示。我发现是reader.read()导致的问题。我该如何仅获取接收到的数据量,然后在最后获取blob URL呢?
谢谢。

哪一行引发了这个错误? - ggorlen
@ggorlen 这一行是 const blob = await response.blob(); - chiliNUT
1个回答

3

更新:

我的第一次尝试是在下载这些块时收集它们,然后再将它们放回去,内存占用很大(视频大小的2-3倍)。使用一个ReadableStream有更低的内存占用(对于1.1GB的mkv文件,内存使用量大约为150MB)。代码主要从这里的片段进行了适应,并仅有我进行了少量修改:

https://github.com/AnthumChris/fetch-progress-indicators/blob/master/fetch-basic/supported-browser.js

<div id="progress_bar"></div>
<video id="video_player"></video>

    const elProgress = document.getElementById('progress_bar'),
        player = document.getElementById('video_player');

    function getVideo2() {
        let contentType = 'video/mp4';
        fetch('$pathToVideo.mp4')
            .then(response => {

                const contentEncoding = response.headers.get('content-encoding');
                const contentLength = response.headers.get(contentEncoding ? 'x-file-size' : 'content-length');
                contentType = response.headers.get('content-type') || contentType;
                if (contentLength === null) {
                    throw Error('Response size header unavailable');
                }

                const total = parseInt(contentLength, 10);
                let loaded = 0;

                return new Response(
                    new ReadableStream({
                        start(controller) {
                            const reader = response.body.getReader();

                            read();

                            function read() {
                                reader.read().then(({done, value}) => {
                                    if (done) {
                                        controller.close();
                                        return;
                                    }
                                    loaded += value.byteLength;
                                    progress({loaded, total})
                                    controller.enqueue(value);
                                    read();
                                }).catch(error => {
                                    console.error(error);
                                    controller.error(error)
                                })
                            }
                        }
                    })
                );
            })
            .then(response => response.blob())
            .then(blob => {
                let vid = URL.createObjectURL(blob);
                player.style.display = 'block';
                player.type = contentType;
                player.src = vid;
                elProgress.innerHTML += "<br /> Press play!";
            })
            .catch(error => {
                console.error(error);
            })
    }

    function progress({loaded, total}) {
        elProgress.innerHTML = Math.round(loaded / total * 100) + '%';
    }

第一次尝试(较差,适用于较小的文件)

我的原始方法。对于一个1.1GB的mkv文件,在文件下载时,内存使用率会慢慢上升到1.3GB,当块被合并时,则会急剧飙升至约3.5Gb。一旦视频开始播放,选项卡的内存使用量会回落到约200MB,但Chrome的整体使用量仍保持在1GB以上。

你可以通过累加视频的每个块(value)来构建blob自己,而不是调用response.blob()来获取blob。这个示例是从这里改编的:https://javascript.info/fetch-progress#0d0g7tutne

        //...
        receivedLength += value.length;
        chunks.push(value);
        //...
        // ==> put the chunks into a Uint8Array that the Blob constructor can use
        let Uint8Chunks = new Uint8Array(receivedLength), position = 0;
        for (let chunk of chunks) {
            Uint8Chunks.set(chunk, position);
            position += chunk.length;
        }

        // ==> you may want to get the mimetype from the content-type header
        const blob = new Blob([Uint8Chunks], {type: 'video/mp4'})

这看起来可以解决问题,但最终不会使用更多的资源吗?这个项目的视频将超过2GB,因此它将使用2GB的存储空间+块上的2GB存储空间。有没有一种不使用更多存储空间的方法? - ChimpKey
@ChimpKey 很好的观点。我的原始答案是一个内存猪,峰值约为视频大小的3倍。我找到了一种更好的方法。我流式传输了一个1.1GB的视频,内存使用量保持在约150MB左右。 - chiliNUT
如果@ChimpKey的问题得到解决,请考虑接受此答案,以便让未来的观众知道这个答案可以解决问题。 - chiliNUT
@ChimpKey 谢谢!我在研究这个过程中学到了很多关于“获取”和“流”的知识。 - chiliNUT

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接