一次大批量处理视频文件的性能优化记录

功能描述

这个功能是这样,有一个视频文件的管理页面,可以选择多个视频后,进行上传。问题出在了多个(100以上)视频文件选择的这一步,部分电脑可能会卡顿甚至卡死。

我顺着代码捋了一下,大概代码如下:

  1. 视频文件选择使用的组件是 element-plus 的 Upload 组件,可以通过 on-change 传入在文件发生变化后执行的方法。
  2. 在on-change中,通过 getFileMd5 获取文件的 MD5 信息;
  3. 在on-change中,通过 getVideoInfo 获取视频文件的时长、宽、高信息;

我现在自己的电脑上,通过浏览器的任务管理器看了一下, getFileMd5 和 getVideoInfo 都会造成浏览器的占用的CPU和内存过大的问题。

看下这两个方法是如何实现的:

js 复制代码
import SparkMD5 from 'spark-md5'

// 获取文件的 MD5
export const getFileMd5 = file => {
  return new Promise((resolve, reject) => {
    const spark = new SparkMD5.ArrayBuffer()
    const reader = new FileReader()

    reader.onload = function (e) {
      // 使用 SparkMD5 计算 md5
      spark.append(e.target.result) 
      const md5 = spark.end() // 完成计算
      resolve(md5)
    }

    reader.onerror = function (error) {
      reject(error)
    }

    // 读取文件为 ArrayBuffer
    reader.readAsArrayBuffer(file)
  })
}
js 复制代码
// 获取视频的时长、宽度和高度
export const getVideoInfo = file => {
  return new Promise((resolve, reject) => {
    const url = URL.createObjectURL(file) // 创建一个指向该视频文件的 url
    const video = document.createElement('video')
    video.src = url

    video.onloadedmetadata = () => {
      resolve({ 
        duration: video.duration, // 获取时长
        width: video.videoWidth, // 获取宽度
        height: video.videoHeight // 获取高度
      })
      URL.revokeObjectURL(url) // 释放URL对象
    }

    video.load() // 开始加载视频文件
  })
}

优化 MD5 的获取方法

主要思路是:通过将文件拆分为多个块,最后再合并计算最终的 MD5。这样,即使处理大文件,也不会导致浏览器的占用过大。

js 复制代码
// 主要思路,将文件分多次 spark.append,避免一次性处理
const getFileMd5 = file => {
  return new Promise((resolve, reject) => {
    const chunkSize = 1024 * 1024 * 2; // 拆分的大小
    const spark = new SparkMD5.ArrayBuffer()
    const reader = new FileReader()
    let currentChunk = 0;
    const totalChunks = Math.ceil(file.size / chunkSize);

    function loadNextChunk() {
        const start = currentChunk * chunkSize;
        const end = Math.min(start + chunkSize, file.size);
        const chunk = file.slice(start, end); // 拆分文件

        reader.onload = (e) => {
            spark.append(e.target.result); 
            currentChunk++;
            // 如果未处理完,则继续处理
            if (currentChunk < totalChunks) {
                loadNextChunk(); 
            } else {
                resolve(spark.end()); // 输出 md5 值
            }
        };

        reader.onerror = (error) => {
            reject(error) ;
        };

        reader.readAsArrayBuffer(chunk); // Read the chunk
    }

    loadNextChunk(); 
  })
}

优化视频信息获取方法

目前没有什么特别好的方法,只能减少 url 创建的次数,减轻一点负担。改进后如下:

js 复制代码
const getVideoInfo = file => {
  return new Promise((resolve, reject) => {
    // 因为 element-plus 的 Upload 组件会默认生成 url,所以就可以省去重复创建 url 的步骤。
    const url = file.url || URL.createObjectURL(file.raw || file)
    const video = document.createElement('video')
    video.src = url
    video.onloadedmetadata = () => {
      resolve({ duration: video.duration, width: video.videoWidth, height: video.videoHeight})
      !file.url && URL.revokeObjectURL(url) // 释放URL对象
    }

    video.load() // 开始加载视频文件
  })
}

限制最大执行队列的数量

上述两个方法在优化完成之后,效果并不显著,经过排查,可能是因为 on-change 事件在同一时间触发次数太多导致的。所以写了一套逻辑,限制了一下最大同时执行的队列。

js 复制代码
// 定义数据
const state = reactive({
  currentList: [], // 正在执行的队列
  maxQueue: 5, // 最大执行队列数量
  waitList:[], // 待执行队列
})

// 响应 element-plus upload 组件的 on-change 事件
function changeHandler (file){
  if(state.currentList.length < state.maxQueue){
    handleOne(file)
  } else {
    state.waitList.push(file)
  }
}

// 单个文件处理
async function handleOne(file){
  state.currentList.push(file)
  const { duration, width, height } = await getVideoInfo(file)
  file.duration = duration
  file.dimension = width + 'x' + height
  file.md5Hash = await getFileMd5(file.raw)
  afterHandler(file)
}

// 执行完成之后,判断是否还有待处理的文件,继续执行
function afterHandler (file){
  state.currentList = state.currentList.filter((fl) => {
    return fl.md5Hash !== file.md5Hash && fl.name !== file.name
  })
  if(state.waitList.length){
    let frist = state.waitList[0]
    state.waitList = state.waitList.filter(fl => fl != frist)
    handleOne(frist)
  }
}

然后根据实际情况,调整 state.maxQueue 的值,来控制最大执行队列数量。

总结

经过以上的优化,可以同时选择的文件数量有了大幅提升,目前测试下来,数量提升到700时,页面也还是比较流畅的,再往上就没有进行更进一步的测试。

所以,对于文件总量较多的场景,还是需要通过分批、分片的形式进行处理,减少并发数量,这样的话,可以有效避免出现浏览器性能问题。

相关推荐
星空寻流年11 分钟前
css3伸缩盒模型第二章(侧轴相关)
javascript·css·css3
GalenWu2 小时前
对象转换为 JSON 字符串(或反向解析)
前端·javascript·微信小程序·json
GUIQU.2 小时前
【Vue】微前端架构与Vue(qiankun、Micro-App)
前端·vue.js·架构
zwjapple2 小时前
“ES7+ React/Redux/React-Native snippets“常用快捷前缀
javascript·react native·react.js
数据潜水员2 小时前
插槽、生命周期
前端·javascript·vue.js
2401_837088502 小时前
CSS vertical-align
前端·html
优雅永不过时·2 小时前
实现一个漂亮的Three.js 扫光地面 圆形贴图扫光
前端·javascript·智慧城市·three.js·贴图·shader
揣晓丹3 小时前
JAVA实战开源项目:健身房管理系统 (Vue+SpringBoot) 附源码
java·vue.js·spring boot·后端·开源
景尘3 小时前
vue3 全局注册自定义指令,input聚焦失焦展示对应值
vue.js
CodeCraft Studio3 小时前
报表控件stimulsoft教程:使用 JoinType 关系参数创建仪表盘
前端·ui