Vue3视频库系统:基于WebWorker的大文件分片上传与播放实现_vue3分片加载视频
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 // 10MBlet 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.tsself.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.tsexport 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. 视频播放功能
播放组件实现
播放控制逻辑
// 播放视频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 Observerconst 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 的视频库系统实现,涵盖了以下关键技术点:
- 大文件分片上传:智能分片策略,支持 1GB 以内视频文件
- WebWorker 并发处理:避免阻塞主线程,提升用户体验
- 响应式状态管理:使用 Vue3 Reactive API 实现状态管理
- 性能优化:懒加载、虚拟滚动、内存管理等多重优化
- 用户体验:完善的错误处理和用户提示
该系统具有以下优势:
- 🚀 高性能:WebWorker 并发处理,不阻塞 UI
- 🛡️ 高可靠:完善的错误处理和重试机制
- 🎯 用户友好:实时进度显示,直观的操作界面
- 🔧 易扩展:模块化设计,易于维护和扩展
通过这套方案,我们成功构建了一个功能完善、性能优异的视频库系统,为用户提供了流畅的视频上传和播放体验。
参考资源
- Vue3 官方文档
- WebWorker API
- Intersection Observer API
- File API
本文基于实际项目开发经验总结,如有问题欢迎交流讨论。