参考文章:
1.https://blog.csdn.net/zrcj0706/article/details/103137617(写的挺好的、就是有几个外部引入的方法没有写全)
最后打算用这个方法,三个外部引入的方法都是关于接口请求的, 整体感觉代码是较为核心的代码,但是像续传部分的代码逻辑不够好,他是直接同步调用接口的,不太好,而且还有他的MD5计算不够精准
计算md5文件的两种方法
2、https://blog.csdn.net/qq_38652871/article/details/88229749
这份代码算是比较全的了,他说自己在项目中试过了,可以参考,也是没有用组件的, 切片上传和断点续传都写好了,里面写的方法也挺好,上传进度条显示,断点续传,视频播放器组件,原生JS的AJAX封装,Promise异步变同步
3.https://www.cnblogs.com/xiahj/p/vue-simple-uploader.html#%E5%85%B3%E4%BA%8Evue-simple-uploader
这个没有试通过,引入的组件是vue-simple-upload,他加上了很多自己的业务组件,感觉直接去看vue-simple-upload的切片上传会更好一点。
自己的总结
感觉吧, 分片上传和断点续传传还挺简单的
流程:
- 获取视频文件,计算文件的md5作为文件标志,询问服务端(MD5可作为文件的唯一标志)
- 有了就算秒传,直接返回url,没有或者有部分上传,就上传片段
- 片段获取通过 file.slice(start, end) 切片,生成blob文件,然后上传
- 在上传完所有分片后,服务端返回字段needMerge
- 调用merge的接口,由服务端合并视频
demo
本地预览切片上传, 本地合并切片上传, 切出来的文件气死就是blob, 文件本身就是blob
可总结的点:
- URL.createObjectURL(file/blob)
- file.slice(start, end) //start, end都是 size
- let fileRederInstance = new FileReader()
fileRederInstance.readAsBinaryString(file)
fileRederInstance.addEventListener(‘load’, e => {
}) // read 文件,addload- fileMD5 = md5(fileBolb)
- new Blob([blob1,blob2,blob3],{type:“image/png”});
<template>
<div>
<el-upload
action
:accept="'video/*'"
:before-upload="beforeAvatarUpload"
>
<el-button>上传视频</el-button>
</el-upload>
<el-button @click="mergeChunks">合并chunks</el-button>
<div v-for="(item, index) in videoUrl" :key="index" >
<video controls style="height: 300px; margin-top: 24px" :src="item"></video>
</div>
</div>
</template>
<script>
/* // 核心方法就三个
fileRederInstance.readAsBinaryString(file)
fileRederInstance.addEventListener('load', e => {
}) */
/* file.slice(start, end) */
/* let fileBolb = e.target.result
this.fileMD5 = md5(fileBolb) */
import md5 from 'js-md5'
import SparkMD5 from 'spark-md5' // 最初使用js-md5计算出来MD5值时不对的,应该和js-MD5没有关系,后来改成了sparkmd5
export default {
data() {
return {
fileMD5: null,
chunkSize: 100 * 1024,
videoUrl: [],
fileChunks: [],
}
},
methods: {
// 这儿计算出来的是整个文件的
md5(file, chunkSize) {
return new Promise((resolve, reject) => {
let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
let chunks = Math.ceil(file.size / chunkSize);
let currentChunk = 0;
let spark = new SparkMD5.ArrayBuffer();
let fileReader = new FileReader();
fileReader.onload = function(e) {
console.log('e', e);
spark.append(e.target.result);
currentChunk++;
if (currentChunk < chunks) {
loadNext();
} else {
let md5 = spark.end();
console.log('md5', md5);
this.fileMD5 = md5;
resolve(md5);
}
};
fileReader.onerror = function(e) {
reject(e);
};
function loadNext() {
let start = currentChunk * chunkSize;
let end = start + chunkSize;
if (end > file.size){
end = file.size;
}
fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
}
loadNext();
});
},
beforeAvatarUpload(file) {
this.chunkCount = Math.ceil(file.size / this.chunkSize) // 总片数
console.log('file', file);
console.log('file.size / this.chunkSize', file.size,this.chunkSize);
console.log('this.chunkCount', this.chunkCount);
this.readFileMD5(file)
},
async readFileMD5(file) {
await this.md5(file, 1024 * 1024 * 10)
this.readChunkMD5(file);
/* console.log('file', file);
// this.videoUrl.push(URL.createObjectURL(file))
let fileRederInstance = new FileReader()
fileRederInstance.readAsBinaryString(file)
fileRederInstance.addEventListener('load', e => {
console.log('e', e);
let fileBolb = e.target.result
// 这儿计算出来的可能只是第一份chunk的md5; 所以这儿要改成spark版本的
this.fileMD5 = md5(fileBolb)
console.log('fileMD5', this.fileMD5); // 这儿未知就获取到了文件的唯一标志, 即使文件名换了也不会改变
}) */
},
readChunkMD5(file) {
console.log('this.chunkCount', this.chunkCount);
for (var i = 0; i < this.chunkCount; i++) {
console.log('i', i);
const { chunk } = this.getChunkInfo(file, i, this.chunkSize)
// 应该用promise all 或者promise顺序执行? 不一定要用promise, 直接一起传, 速率为每个单个的相加(单个的速率的计算有在另外一篇文章中写, 可能不是特别完善)
this.uploadChunk({ chunk, currentChunk: i, chunkCount: this.chunkCount })
}
},
getChunkInfo(file, currentChunk, chunkSize) {
let start = currentChunk * chunkSize
let end = Math.min(file.size, start + chunkSize)
let chunk = file.slice(start, end) //切割出来的还是一个blob,实际上file就是一个blob的实例
console.log('chunk', chunk);
return { start, end, chunk, currentChunk }
},
uploadChunk(chunkInfo){
console.log('chunkInfo', chunkInfo);
this.fileChunks.push(chunkInfo)
// 试验: 是否所有的blob都可以播放呢?
this.videoUrl.push(URL.createObjectURL(chunkInfo.chunk))
// 1、URL.createObjectURL(chunkInfo.chunk) 是可以将blob转换成url的
// 2、 只有第一个blob可以播放,播放时长为 1/chunkCount * 总时长
/* let fetchForm = new FormData()
fetchForm.append('chunk', chunkInfo.currentChunk)
fetchForm.append('chunkSize', this.chunkSize)
fetchForm.append('chunks', chunkInfo.chunkCount)
fetchForm.append('file', chunkInfo.chunk)
fetchForm.append('md5', this.fileMD5) */
},
// 合并blob, 也就是blob.slice的反操作, 合并后的文件可以被正常播放, 如果我们打乱呢? 还能被正常播放吗
mergeChunks() {
const Chunks = this.fileChunks.map(item => item.chunk)
const mergedChunk = new Blob(Chunks,{type:"video/mp4"});
[mergedChunk[0], mergedChunk[1]] = [mergedChunk[1], mergedChunk[0]] // 打乱blob位置, 好像是没有影响的哦
this.videoUrl.push(URL.createObjectURL(mergedChunk))
}
}
}
</script>
<style>
</style>