Vue3视频库系统:基于WebWorker的大文件分片上传与播放实现

Vue3 视频库系统:基于 WebWorker 的大文件分片上传与播放实现

前言

在现代 Web 应用中,视频内容管理是一个常见且重要的功能。本文将详细介绍如何使用 Vue3、TypeScript 和 WebWorker 技术构建一个功能完善的视频库系统,实现大文件分片上传、视频播放、缩略图懒加载等核心功能。

项目概述

核心功能

  • 🎬 视频播放:支持多种格式视频在线播放
  • 📤 大文件上传:基于 WebWorker 的分片上传,支持 1GB 以内视频文件
  • 🔄 并发控制:智能的上传队列管理和并发控制
  • 🖼️ 懒加载:缩略图懒加载优化性能
  • 📊 进度跟踪:实时上传进度显示
  • 🎯 文件管理:重命名、删除等基础操作

技术栈

  • 前端框架:Vue3 + TypeScript
  • UI 组件:Element Plus
  • 状态管理:Vue3 Reactive API
  • 并发处理:WebWorker
  • 文件处理:原生 File API

系统架构设计

整体架构

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   Vue组件层     │    │   服务层        │    │   Worker层      │
│                 │    │                 │    │                 │
│ VideoLibrary    │───▶│ UploadService   │───▶│ upload-worker   │
│ UploadList      │    │ UploadState     │    │                 │
│                 │    │                 │    │                 │
└─────────────────┘    └─────────────────┘    └─────────────────┘

核心模块

1. 上传服务层(UploadService)

负责文件上传的核心逻辑,包括队列管理、Worker 通信等。

2. 状态管理层(UploadState)

使用 Vue3 的 reactive API 管理上传状态,实现响应式数据更新。

3. Worker 处理层(upload-worker)

在独立线程中处理文件分片和上传,避免阻塞主线程。

核心功能实现

1. 大文件分片上传

分片策略
// 分片配置
const UPLOAD_CONFIG = {
  MAX_BATCH_SIZE: 6, // 每批次分片数量
  WORKER_CONCURRENCY: 3, // Worker内并发数
  BATCH_TIMEOUT: 600000, // 批次超时时间
  CHUNK_SIZE: 10 * 1024 * 1024, // 分片大小:10MB
  MIN_CHUNK_SIZE: 5 * 1024 * 1024 // 最小分片:5MB
}
分片计算逻辑
// 计算分片大小
const standardChunkSize = UPLOAD_CONFIG.CHUNK_SIZE // 10MB
let totalChunks = Math.ceil(file.size / standardChunkSize)
const finalChunkSizes: number[] = []

// 确保最后一个分片不小于最小限制
if (totalChunks > 1) {
  const lastChunkSize = file.size % standardChunkSize
  if (lastChunkSize > 0 && lastChunkSize < UPLOAD_CONFIG.MIN_CHUNK_SIZE) {
    // 将最后一个分片与倒数第二个分片合并
    totalChunks = totalChunks - 1
    const mergedLastChunkSize = standardChunkSize + lastChunkSize

    // 设置分片大小
    for (let i = 0; i < totalChunks - 1; i++) {
      finalChunkSizes.push(standardChunkSize)
    }
    finalChunkSizes.push(mergedLastChunkSize)
  } else {
    // 正常分片
    for (let i = 0; i < totalChunks; i++) {
      finalChunkSizes.push(
        i === totalChunks - 1 ? file.size % standardChunkSize || standardChunkSize : standardChunkSize
      )
    }
  }
}

2. WebWorker 并发处理

Worker 消息处理
// upload-worker.ts
self.onmessage = async (event) => {
  const {type, payload} = event.data

  switch (type) {
    case 'UPLOAD_BATCH':
      await handleBatchUpload(payload)
      break
    case 'COMPLETE_FILE':
      handleFileComplete(payload)
      break
    case 'CANCEL_UPLOAD':
      handleCancelUpload(payload.uploadId)
      break
  }
}
并发上传控制
// 并发上传实现
async function uploadChunksConcurrently(
  chunks: Array<{chunk: ArrayBuffer; chunkIndex: number}>,
  fileName: string,
  uploadId: string,
  fileId: string,
  totalChunks: number,
  progress: ProgressInfo
) {
  // 创建所有上传Promise
  const uploadPromises = chunks.map(({chunk, chunkIndex}) =>
    uploadChunkWithProgress(chunk, chunkIndex, fileName, uploadId, fileId, totalChunks, progress)
  )

  // 并发执行所有上传任务
  await Promise.all(uploadPromises)
}

3. 响应式状态管理

状态定义
// uploadState.ts
export interface FileInfo {
  id: string
  fileName: string
  fileSize: string
  status: 'queued' | 'uploading' | 'success' | 'error'
  progress: number
  error?: string
}

export const uploadState = reactive<UploadState>({
  files: {}
})
状态更新方法
// 更新上传进度
export function updateProgressById(fileId: string, progress: number): void {
  const file = uploadState.files[fileId]
  if (file && file.status !== 'success') {
    if (file.status === 'queued') {
      file.status = 'uploading'
    }
    file.progress = progress
  }
}

// 设置上传成功
export function setUploadSuccessById(fileId: string): void {
  const file = uploadState.files[fileId]
  if (file) {
    file.status = 'success'
    file.progress = 100
  }
}

4. 视频播放功能

播放组件实现
<template>
  <!-- 视频播放对话框 -->
  <el-dialog
    v-model="dialogVisible"
    title="视频播放"
    width="70%"
    :before-close="closeVideoDialog"
    destroy-on-close
    :close-on-click-modal="false"
    class="video-dialog"
    :modal="false"
  >
    <div class="video-player-container">
      <video
        ref="videoPlayerRef"
        :src="currentVideoUrl"
        controls
        class="video-player"
        @contextmenu.prevent
        :disablePictureInPicture="true"
        controlsList="nodownload noremoteplayback noplaybackrate"
      ></video>
    </div>
  </el-dialog>
</template>
播放控制逻辑
// 播放视频
const playVideo = (item: VideoItem) => {
  if (item.status === 2) {
    return ElMessage.warning('视频正在加载中,请稍后再试')
  }

  currentVideoUrl.value = getVideoUrl(item.fileId)
  dialogVisible.value = true

  // 确保视频元素已渲染后播放
  setTimeout(() => {
    if (videoPlayerRef.value) {
      videoPlayerRef.value.play().catch(() => {
        // 播放失败处理
      })
    }
  }, 100)
}

// 关闭视频对话框
const closeVideoDialog = () => {
  dialogVisible.value = false
  if (videoPlayerRef.value) {
    videoPlayerRef.value.pause()
    videoPlayerRef.value.currentTime = 0
  }
}

5. 缩略图懒加载

Intersection Observer 实现
// 初始化Intersection Observer
const initIntersectionObserver = () => {
  observer = new IntersectionObserver(
    (entries) => {
      entries.forEach((entry) => {
        const id = entry.target.id.split('-')[1]
        const item = videoData.value.find((v) => v.id.toString() === id)

        if (item) {
          // 当元素进入视口时,设置为可见
          item.isVisible = entry.isIntersecting
        }
      })
    },
    {
      root: null,
      rootMargin: '100px', // 提前100px加载
      threshold: 0.1 // 当10%的元素可见时触发
    }
  )
}
滚动优化
// 滚动事件处理(节流)
const handleScroll = (() => {
  let timer: ReturnType<typeof setTimeout> | null = null

  return () => {
    if (!timer) {
      timer = setTimeout(() => {
        // 重新检查可见性
        const entries = document.querySelectorAll('[id^="thumbnail-"]')
        entries.forEach((el) => {
          const rect = el.getBoundingClientRect()
          const isVisible =
            rect.top <= (window.innerHeight || document.documentElement.clientHeight) + 100 && rect.bottom >= -100

          const id = el.id.split('-')[1]
          const item = videoData.value.find((v) => v.id.toString() === id)
          if (item) {
            item.isVisible = isVisible
          }
        })
        timer = null
      }, 200)
    }
  }
})()

6. 文件管理功能

重命名功能
const handleCommand = async (type: string, item: VideoItem) => {
  if (type === 'rename') {
    // 保存原始文件名
    item.originalFileName = item.fileName
    item.isEdit = true

    nextTick(() => {
      const inputs = document.querySelectorAll('.video-info .el-input__inner')
      const lastInput = inputs[inputs.length - 1] as HTMLInputElement
      if (lastInput) {
        lastInput.focus()
        // 选中文件名(不包含扩展名)
        const fileName = item.fileName
        const dotIndex = fileName.lastIndexOf('.')
        if (dotIndex > 0) {
          lastInput.setSelectionRange(0, dotIndex)
        } else {
          lastInput.select()
        }
      }
    })
  }
}

// 保存重命名
const handleSaveRename = async (item: VideoItem) => {
  const data = {
    id: item.id,
    fileName: item.fileName
  }
  await api.rename(data)
  ElMessage.success('重命名成功')
  item.isEdit = false
  query()
}
删除功能
const handleDelete = async (item: VideoItem) => {
  try {
    await ElMessageBox.confirm(`确定删除视频${item.fileName}吗?`, '提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    })

    await api.delete(item.id)
    ElMessage.success('删除成功')
    query()
  } catch (error) {
    // 用户取消操作
  }
}

性能优化策略

1. 上传性能优化

分片大小优化
  • 标准分片:10MB,平衡上传效率和内存占用
  • 最小分片:5MB,避免过小分片影响性能
  • 智能合并:最后一个分片过小时自动合并
并发控制
  • 批次处理:每批次最多 6 个分片
  • Worker 并发:每个批次内最多 3 个并发上传
  • 队列管理:智能的上传队列,避免资源争抢

2. 渲染性能优化

懒加载策略
// 缩略图懒加载
<img v-if="item.thumbnailFileId && item.isVisible"
     :src="getVideoUrl(item.thumbnailFileId)"
     alt="视频缩略图" />
<img v-else-if="!item.thumbnailFileId"
     src="@/assets/images/video/video-loading.png"
     alt="视频缩略图" />
虚拟滚动
  • 使用 Intersection Observer 监听元素可见性
  • 只渲染可视区域内的缩略图
  • 提前 100px 预加载,提升用户体验

3. 内存管理

Worker 内存优化
// 文件上传完成后清理
function handleFileComplete(payload: any) {
  // 清理文件进度信息
  fileProgress.delete(uploadId)
  cancelledFiles.delete(uploadId)

  // 清理AbortController
  abortControllers.delete(uploadId)
}
组件卸载清理
onUnmounted(() => {
  // 断开观察器连接
  if (observer) {
    observer.disconnect()
    observer = null
  }

  // 移除事件监听
  window.removeEventListener('scroll', handleScroll)
  window.removeEventListener('resize', handleScroll)
})

总结

本文详细介绍了基于 Vue3 和 WebWorker 的视频库系统实现,涵盖了以下关键技术点:

  1. 大文件分片上传:智能分片策略,支持 1GB 以内视频文件
  2. WebWorker 并发处理:避免阻塞主线程,提升用户体验
  3. 响应式状态管理:使用 Vue3 Reactive API 实现状态管理
  4. 性能优化:懒加载、虚拟滚动、内存管理等多重优化
  5. 用户体验:完善的错误处理和用户提示

该系统具有以下优势:

  • 🚀 高性能:WebWorker 并发处理,不阻塞 UI
  • 🛡️ 高可靠:完善的错误处理和重试机制
  • 🎯 用户友好:实时进度显示,直观的操作界面
  • 🔧 易扩展:模块化设计,易于维护和扩展

通过这套方案,我们成功构建了一个功能完善、性能优异的视频库系统,为用户提供了流畅的视频上传和播放体验。

参考资源


本文基于实际项目开发经验总结,如有问题欢迎交流讨论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值