视频上传 - 断点续传那点事

在上一篇文章中,我们讲解了分片上传的实现方式。在讲解断点续传之前,我要把上篇文章中留下的问题讲解一下。读过上一篇文章的小伙伴们都知道,对于分片上传来说,它的传输方式分为2种,一种是按顺序传输,一种是并发的方式传输

上一篇里,我们只讲解了第一种方式,第二种方式并没有过多的去讲解。接下来,小编会带着大家,一步步拆解这个内容。大家系好安全带,面试题要来了。

如何实现分片的并发上传?

大家肯定都刷到过这样的面试题:给你100张图片,如何实现并发上传?

首先我们要知道,浏览器一次性能发送多少个请求?答案是6个。那也就是说,我们需要设计一个流程,每次并发6个,直到所有的请求都完成响应。

那如何实现并发呢?

Promise似乎提供了一些方法,比如 Promise.allSettledPromise.allPromise.race

那这个时候,面试题又来了,它三的区别是什么在这个场景里,使用哪个API更合理

共同点:参数都是由Promise实例组成的数组。

不同点:返回值不同。Promise.allSettled返回的是所有实例的状态数组。all与race返回的都是对象,最先成功或者失败的对象。

在我们这个场景里,我们优先选择 Promise.allSettled。知道了并发的方式还远远不够,假设我们有10个地址,一次并发发送6个请求,所以需要2次并发6个请求。那这个2次该如何得知呢?最简单也最实用的方案就是通过Array.splice方法来切割数组。因为splice进行删除操作时会影响到原数组,也就是原数组里也没有了被删除的元素。那我们就可以通过while循环来判断原数组是否还有剩余元素。没有剩余元素意味着并发请求结束了。

这个时候一个知识点也过来了。一起复习一下,slice与splice有什么区别

  • 作用不一样。slice只能用来删除元素。splice可通过参数来决定删除或者添加元素的操作。
  • 对原数组的影响不一样。slice不会影响原数组。无论splice进行的是删除操作 还是 添加操作,它都会影响到原数组。

讲了这么多理论,也是时候动手实践一波了:

javascript 复制代码
let arr = [
    {name: 1},
    {name: 2},
    {name: 3},
    {name: 4},
    {name: 5},
    {name: 6},
    {name: 7},
    {name: 8},
    {name: 9},
    {name: 10},
    {name: 11},
];

async function ax(){
    while(arr.length > 0){
        let newArr = arr.splice(0, 6);
        let result = await Promise.allSettled(
            newArr.map(item => {
                return new Promise((resolve, reject) => {
                    setTimeout(
                        () => {
                            return resolve(item.name)
                        },
                        1000
                    )
                })
            })
        );
        console.log('result是撒谎:', result);
    }
}

ax();

执行一下上述代码,我们会发现,按照了我们的预期,过了1s,控制台同时打印了1-6。再过了1s,控制台同时的打印了剩余元素。

那我们把这个思想转换到 分片上传的代码里。

jsx 复制代码
// Video组件里的其他内容均不变,有不清晰的请看前2篇文章 ==========

// 并发分片上传
concurrencyUploadChunkFile = async () => {
    let self = this;
    // 定义每块体积大小为5MB
    let chunk_size = 5 * 1024 * 1024;
    // 当前上传的文件对象
    let fileObj = this.state.fileObj;
    // 当前上传的文件对象的体积
    let allSize = this.state.fileObj.size;
    // 当前文件被切割的总分片数量
    let allChunkCount = Math.ceil(allSize / chunk_size);
    
    // chunk文件集合
    let chunkArr = [];
    for (let index = 0; index < allChunkCount; index++){
        let startIndex = index * chunk_size;
        let endIndex = Math.min(startIndex + chunk_size, allSize);
        chunkArr.push({
            data: fileObj.slice(
                index * chunk_size,
                endIndex
            ),
            filename: `chunk-${index}`,
            chunkIndex: index
        });
    }

    // 开始并发分片上传
    while(chunkArr.length > 0){
        let reqArr = chunkArr.splice(0, 6);
        let reqArrFactory = reqArr.map(
            item => {
                return new Promise(
                    (resolve, reject) => {
                        let result = this.uploadChunkReq(item.data, item.chunkIndex, 'chunk');
                        return resolve(result);
                    }
                )
            }
        )
        let resArr = await Promise.allSettled(reqArrFactory);
        console.log('6个请求的响应如何:', resArr);
    }

    // 合并请求
    // this.mergeChunk();
    
    render(){
        return <div>
           <button onClick={this.concurrencyUploadChunkFile}>并发上传</button>
        </div>
    }

}

在上述代码中,我们定义了分片的体积为5M,此时假如我们分片上传了一个61M的视频,根据计算,会得到13个分片,如果我们控制网速,能明显的看到分层才对。因为毕竟发了3波,每波都是发送6个请求,每波之间都是按顺序的。就像下面这样:

并发上传分片到这里就结束啦。但如果想扩展的话,其实还有很多考题,就拿并发来举例子,大家可能还会听到一些考题,比如实现并发的方法有哪些?Promise.allSetlled是如何实现的等等。

我想说的是,大家不要去刻意背这些东西,因为这些题的本质就一个,就是js异步对象的运行机制。当你深入理解了就会发现,真的是举一反三。

断点续传

直到目前为止,大家有没有发现,我们进展的过于顺利,如果在上传分片的过程中出现了失误,我们应该怎么办?

断点续传是一个防错机制。它在大文件上传失败时,不必重新来过,从断掉的地方重新上传即可。

如何实现呢?很简单,从哪个分片断掉,就从哪块上传呗。

但是大家也会发现,即使是最简单的场景,根据每个项目实现分片上传的方式不同,断点续传的方法也不尽一样。因为断点续传的方式不一样,所以导致后端实现合并分片的方式也会随之改变。举个例子:

假如,我们有13个分片,序号分为是1-13,先并发1-6,此时我们发现分片6失败了,然后我们维护一个失败分片的数组,将分片6 push进去。接着并发 7 -12,此时都成功了。最后发送13,也成功了。因为分片6失败了,所以我们还需要再发送分片6,最后发现分片6也成功了。但是对于后端来说,在合并分片的时候就需要新增排序逻辑了,因为分片的序号对不上了。大家懂我的意思吧。

这里我们来新增前端处理断点续传的逻辑。逻辑如下:

  • 定义一个失败的分片集合
  • while循环时,通过判断原分片数组的长度是否为0,来确定所有的分片是否都上传了。
  • 在上传的过程中,如果有分片失败的,就将它push到 失败分片集合里。
  • 当原分片集合为空时,意味着此时应该重传失败的分片了。
javascript 复制代码
// 其他内容不变 =======

// 并发上传分片
concurrencyUploadChunkFile = async () => {
    let self = this;
    // 定义每块体积大小为5MB
    let chunk_size = 5 * 1024 * 1024;
    // 当前上传的文件对象
    let fileObj = this.state.fileObj;
    // 当前上传的文件对象的体积
    let allSize = this.state.fileObj.size;
    let allChunkCount = Math.ceil(allSize / chunk_size);

    // 失败的分片集合(新增++++++++++++++++++++++++++++)
    let failedChunkArr = [];

    // chunk文件集合
    let chunkArr = [];
    
    for (let index = 0; index < allChunkCount; index++){
        let startIndex = index * chunk_size;
        let endIndex = Math.min(startIndex + chunk_size, allSize);
        chunkArr.push({
            data: fileObj.slice(
                index * chunk_size,
                endIndex
            ),
            filename: `chunk-${index}`,
            chunkIndex: index
        });
    }

    // 开始分片上传(update++++++++++++++++++++++++++++++++++++++++)
    while(chunkArr.length > 0 || failedChunkArr.length > 0){
        let reqArr = chunkArr.length > 0 ? chunkArr.splice(0, 6) : failedChunkArr.splice(0, 6);
        let reqArrFactory = reqArr.map(
            item => {
                return new Promise(
                    (resolve, reject) => {
                        let result = this.uploadChunkReq(item.data, item.chunkIndex, 'chunk');
                        if (result?.code != 200){
                            failedChunkArr.push(item);
                        }
                        return resolve(result);
                    }
                )
            }
        )
        let resArr = await Promise.allSettled(reqArrFactory);
        console.log('6个请求的响应如何:', resArr);
    }

    // 合并请求
    // this.mergeChunk();

}

最后

好啦,本篇文章到这里就结束啦,这篇文章同样收录在视频处理那些事专栏里,如果上述过程中有明显的错误,欢迎大家指正,希望我讲的对大家有帮助,那么我们下期再见啦,拜拜~~

相关推荐
恋猫de小郭5 分钟前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60617 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了7 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅8 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment8 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端