目录
业务场景
1. promise的基本使用
promise函数
promise.all
async和await的使用
await错误处理
2. await递归调用
3. MP4分片下载
场景:主要针对大视频循环播放的场景,为节省流量,将视频下载至本地播放
业务逻辑:播放mp4大视频,同时分片下载该视频,视频下载完成后转为blob本地链接播放,替换原视频地址,当分片下载失败,N秒后递归下载错误的分片,直至下载成功,如果5次后仍然失败,停止下载,播放原视频地址
注:本文主要解析MP4下载过程
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)
})
}
const p1 = getMenu()
const p2 = getList()
Promise.all([p1, p2]).then(() => {
this.Loading = false
// 更新页面数据
})
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
async function catchErr() {
try {
const menu = await getErr()
}
catch(err) {
console.log(err);
}
}
// err
// 示例:获取数组['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)
})
}
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;
}