video 切片上传探索

参考文章:

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的切片上传会更好一点。

4. js操作Blob合并

自己的总结

感觉吧, 分片上传和断点续传传还挺简单的
流程:

  1. 获取视频文件,计算文件的md5作为文件标志,询问服务端(MD5可作为文件的唯一标志)
  2. 有了就算秒传,直接返回url,没有或者有部分上传,就上传片段
  3. 片段获取通过 file.slice(start, end) 切片,生成blob文件,然后上传
  4. 在上传完所有分片后,服务端返回字段needMerge
  5. 调用merge的接口,由服务端合并视频
demo

本地预览切片上传, 本地合并切片上传, 切出来的文件气死就是blob, 文件本身就是blob
可总结的点:

  1. URL.createObjectURL(file/blob)
  2. file.slice(start, end) //start, end都是 size
  3. let fileRederInstance = new FileReader()
    fileRederInstance.readAsBinaryString(file)
    fileRederInstance.addEventListener(‘load’, e => {
    }) // read 文件,addload
  4. fileMD5 = md5(fileBolb)
  5. 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>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 Spring Boot 中实现切片上传视频,需要进行以下步骤: 1. 前端切片上传:前端将视频文件进行切片,使用 Form Data 将切片文件上传到后端。可以使用 axios 或者 jQuery 的 ajax 进行文件上传。 2. 后端接收切片文件:后端接收前端上传切片文件,可以使用 Spring Boot 的 MultipartFile 类型接收文件流。 3. 后端合并切片文件:将接收到的切片文件进行合并,生成完整的视频文件。可以使用 RandomAccessFile 类型进行文件合并。 4. 前端进度条展示:前端可以通过监听上传事件,实时展示上传进度条。 5. 后端断点续传:可以为切片上传添加断点续传功能,保证上传的可靠性和完整性。 示例代码如下: 前端代码: ```javascript // 切片上传 function uploadFile() { var file = document.getElementById("file").files[0]; var chunkSize = 1024 * 1024; // 切片大小,这里设置为 1MB var chunks = Math.ceil(file.size / chunkSize); // 切片总数 var currentChunk = 0; // 当前切片编号 var fileReader = new FileReader(); var formData = new FormData(); var xhr = new XMLHttpRequest(); xhr.open("POST", "/video/upload", true); fileReader.onload = function (e) { formData.append("file", new Blob([e.target.result])); formData.append("name", file.name); formData.append("chunks", chunks); formData.append("currentChunk", currentChunk); formData.append("chunkSize", chunkSize); xhr.upload.onprogress = function (event) { if (event.lengthComputable) { var complete = (event.loaded / event.total * 100 | 0); console.log("complete:" + complete + "%"); } }; xhr.onreadystatechange = function () { if (xhr.readyState === 4 && xhr.status === 200) { console.log(xhr.responseText); currentChunk++; if (currentChunk < chunks) { fileReader.readAsArrayBuffer(file.slice(currentChunk * chunkSize, (currentChunk + 1) * chunkSize)); } else { console.log("上传完成"); } } }; xhr.send(formData); }; fileReader.readAsArrayBuffer(file.slice(0, chunkSize)); } ``` 后端代码: ```java @RestController @RequestMapping("/video") public class VideoController { private static final String UPLOAD_DIR = "upload/"; @PostMapping("/upload") public String upload(@RequestParam("file") MultipartFile file, @RequestParam("name") String name, @RequestParam("chunks") int chunks, @RequestParam("currentChunk") int currentChunk, @RequestParam("chunkSize") int chunkSize) { try { File dir = new File(UPLOAD_DIR); if (!dir.exists()) { dir.mkdirs(); } File chunkFile = new File(UPLOAD_DIR + name + "_" + currentChunk); IOUtils.copy(file.getInputStream(), new FileOutputStream(chunkFile)); if (currentChunk == chunks - 1) { File targetFile = new File(UPLOAD_DIR + name); FileOutputStream outputStream = new FileOutputStream(targetFile, true); for (int i = 0; i < chunks; i++) { File tempFile = new File(UPLOAD_DIR + name + "_" + i); FileInputStream inputStream = new FileInputStream(tempFile); byte[] buffer = new byte[(int) tempFile.length()]; inputStream.read(buffer); outputStream.write(buffer); inputStream.close(); tempFile.delete(); } outputStream.close(); } return "success"; } catch (IOException e) { e.printStackTrace(); return "error"; } } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值