实现MP4分片下载 + Promise的使用

目录

        业务场景

        1. promise的基本使用

                    promise函数

                    promise.all

                    async和await的使用

                    await错误处理

        2. await递归调用

        3. MP4分片下载


业务场景

场景:主要针对大视频循环播放的场景,为节省流量,将视频下载至本地播放

业务逻辑:播放mp4大视频,同时分片下载该视频,视频下载完成后转为blob本地链接播放,替换原视频地址,当分片下载失败,N秒后递归下载错误的分片,直至下载成功,如果5次后仍然失败,停止下载,播放原视频地址

注:本文主要解析MP4下载过程

1. promise的基本使用

promise函数
function getUser() {
   return new Promise((resolve, reject) => {
      setTimeout(() => resolve({id:10}), 200)
  })
}
function getMenu() {
   return new Promise((resolve, reject) => {
      setTimeout(() => resolve('menu'), 200)
  })
}
function getList() {
   return new Promise((resolve, reject) => {
      setTimeout(() => resolve('list'), 200)
  })
}
function getErr() {
   return new Promise((resolve, reject) => {
      setTimeout(() => reject('error'), 200)
  })
}
promise.all
const p1 = getMenu()
const p2 = getList()
Promise.all([p1, p2]).then(() => {
   this.Loading = false
   // 更新页面数据
})
async和await的使用

awiat返回的是promise中resolve中的值

如果await后面是async函数,返回async的return值

async function steps(){
    const user = await getUser()
    const menu = await getMenu(user.id)
    console.log(menu)
    const list = await getList(user.id)
    console.log(list)
}
// menu
// list
await错误处理
async function catchErr() {
	try {
		const menu = await getErr()
      } 
    catch(err) {
        console.log(err);
      }
}
// err

2. await递归调用

// 示例:获取数组['list', 'list', 'list', 'list', 'list']
async function getTotal(){
    const newList = await singleList(0,[])
    // 执行完成
    console.log(222222, newList)
}

// 递归获取整个数组
async function singleList(index,list){
    if(index < 5){
        const single = await getList()
        list.push(single)
        await singleList(index + 1, list)
    }
    return list
}

function getList() {
    return new Promise((resolve, reject) => {
       setTimeout(() => resolve('list'), 200)
   })
 }

3. MP4分片下载

const blobSrc = await rangeDownload(item);
console.log('视频链接:', blobSrc )
// 分片大小10M
const RANGE_SIZE = 1024 * 1024 * 10;
// 失败重试次数
const FAIL_TOTAL = 5;
// 重试间隔时间
const WAIT_TIME = 3000;
// 当前重试次数
let failNum = 0;
// 定时器
let downTimer = null;

// MP4大视频分片下载
async function rangeDownload(fileUrl) {
    console.log('开始下载' + fileUrl, new Date().toLocaleTimeString());
    // 1.获取视频的长度
    const contentLength = await getContentLength(fileUrl);
    // 2.计算片数
    const numberRequest = Math.ceil(contentLength / RANGE_SIZE);
    // 3.新建二进制数据流数组
    const arrayBufferArray = new Array(numberRequest).fill(null);
    // 4.下载分片,并合并转为blob地址
    const blobSrc = await allDownload(arrayBufferArray, fileUrl, numberRequest);
    return blobSrc;
}
// 递归下载所有失败的分片,成功后拼接数据流转为blob链接
async function allDownload(arrayBufferArray, fileUrl, numberRequest) {
    // 下载超过N次停止下载
    if (failNum >= FAIL_TOTAL) {
        console.log(`下载${FAIL_TOTAL}次失败`);
        // 使用原视频连接
        return fileUrl;
    } else {
        failNum++;
        // 判断返回值是否为null,如果是null,等待3s重新下载分片,递归直到所有分片下载完成
        return new Promise((resolve, reject) => {
            if (downTimer) clearTimeout(downTimer);
            downTimer = setTimeout(async () => {
                for (let i = 0; i < numberRequest; i++) {
                    // 如果是null下载本分片
                    if (!arrayBufferArray[i]) {
                        const startIndex = i * RANGE_SIZE;
                        const endIndex = startIndex + RANGE_SIZE - 1;
                        arrayBufferArray[i] = await getRangeContent(startIndex, endIndex, fileUrl);
                    }
                }
                // 重新下载后还是有失败的,开始递归下载直到所有文件下载完成
                const index = arrayBufferArray.findIndex(item => !item);
                if (index > -1) {
                    console.log('部分分片下载失败,继续递归下载:', index);
                    const blobSrc = await allDownload(arrayBufferArray, fileUrl, numberRequest);
                    resolve(blobSrc);
                } else {
                    // 所有分片下载成功,合并分片
                    console.log('分片全部下载成功');
                    clearTimeout(downTimer);
                    const result = concatArrayBuffer(arrayBufferArray);
                    const blob = new Blob([result], { type: 'application/octet-stream' });
                    const blobSrc = URL.createObjectURL(blob);
                    resolve(blobSrc);
                }
            }, WAIT_TIME);
        });
    }
}

// 获取视频文件大小
function getContentLength(url) {
    return new Promise((resolve, reject) => {
        var xhr = new XMLHttpRequest();
        xhr.open('HEAD', url, true);
        xhr.onreadystatechange = function() {
            if (this.readyState == 4) {
                const size = xhr.getResponseHeader('Content-Length');
                resolve(size);
            }
        };
        xhr.send();
    });
}

// 下载单个分片内容
function getRangeContent(startIndex, endIndex, url) {
    return new Promise((resolve, reject) => {
        var xhr = new XMLHttpRequest();
        xhr.open('GET', url, true);
        xhr.setRequestHeader('Range', `bytes=${startIndex}-${endIndex}`);
        xhr.responseType = 'arraybuffer';
        xhr.onreadystatechange = function() {
            if (this.readyState == 4) {
                resolve(this.response);
            }
        };
        xhr.onerror = function(err) {
            // 失败后填充null
            resolve(null);
        };
        xhr.send();
    });
}

// 合并切片文件
function concatArrayBuffer(arrayBufferArray) {
    let totalLength = 0;
    arrayBufferArray.forEach(arrayBuffer => {
        totalLength += arrayBuffer.byteLength;
    });
    const result = new Uint8Array(totalLength);
    let offset = 0;
    arrayBufferArray.forEach(arrayBuffer => {
        result.set(new Uint8Array(arrayBuffer), offset);
        offset += arrayBuffer.byteLength;
    });
    return result;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值