# 如何使用Vue实现录制视频并压缩视频文件
## 目录
1. [前言](#前言)
2. [技术选型与准备工作](#技术选型与准备工作)
3. [搭建Vue项目环境](#搭建vue项目环境)
4. [实现视频录制功能](#实现视频录制功能)
5. [视频压缩技术实现](#视频压缩技术实现)
6. [完整代码实现](#完整代码实现)
7. [性能优化与注意事项](#性能优化与注意事项)
8. [兼容性与错误处理](#兼容性与错误处理)
9. [总结与扩展](#总结与扩展)
## 前言
在Web应用中实现视频录制和压缩是一个常见但具有挑战性的需求。本文将详细介绍如何使用Vue.js框架配合现代浏览器API实现完整的视频录制流程,并重点讲解如何通过前端技术对视频文件进行有效压缩。
随着WebRTC技术的普及和浏览器能力的提升,现代Web应用已经能够在不依赖插件的情况下实现复杂的多媒体操作。根据StatCounter统计,全球超过92%的浏览器已支持WebRTC相关API,这为我们在前端实现音视频处理提供了坚实基础。
## 技术选型与准备工作
### 核心技术栈
- **Vue 3**:使用Composition API实现更清晰的逻辑组织
- **MediaDevices API**:访问摄像头和麦克风
- **MediaRecorder API**:实现视频录制
- **FFmpeg.wasm**:在浏览器中进行视频压缩处理
- **File API**:处理二进制视频数据
### 环境要求
- 现代浏览器(Chrome 76+、Firefox 68+、Edge 79+)
- Node.js 14+环境
- 可用的摄像头设备
### 安装必要依赖
```bash
npm install vue@next ffmpeg.js @ffmpeg/ffmpeg @ffmpeg/core
vue create video-recorder
cd video-recorder
/src
/components
VideoRecorder.vue
VideoPlayer.vue
/utils
video-compressor.js
App.vue
main.js
在public
目录下添加FFmpeg核心文件:
// 在main.js中初始化
import { createFFmpeg } from '@ffmpeg/ffmpeg'
const ffmpeg = createFFmpeg({ log: true })
app.config.globalProperties.$ffmpeg = ffmpeg
async function getMediaStream(constraints) {
try {
return await navigator.mediaDevices.getUserMedia(constraints)
} catch (err) {
console.error('获取媒体设备失败:', err)
throw err
}
}
function setupMediaRecorder(stream, options) {
const mediaRecorder = new MediaRecorder(stream, options)
const recordedChunks = []
mediaRecorder.ondataavailable = (event) => {
if (event.data.size > 0) {
recordedChunks.push(event.data)
}
}
return {
mediaRecorder,
getRecordedBlob: () => new Blob(recordedChunks, { type: options.mimeType })
}
}
<template>
<div class="recorder-container">
<video ref="videoPreview" autoplay muted></video>
<button @click="startRecording">开始录制</button>
<button @click="stopRecording" :disabled="!isRecording">停止录制</button>
<div v-if="recordedUrl">
<video :src="recordedUrl" controls></video>
<button @click="compressVideo">压缩视频</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
stream: null,
mediaRecorder: null,
recordedUrl: null,
isRecording: false,
videoBlob: null
}
},
methods: {
async startRecording() {
try {
this.stream = await navigator.mediaDevices.getUserMedia({
video: { width: 1280, height: 720 },
audio: true
})
this.$refs.videoPreview.srcObject = this.stream
const options = {
mimeType: 'video/webm;codecs=vp9',
videoBitsPerSecond: 2500000
}
const { mediaRecorder, getRecordedBlob } = setupMediaRecorder(this.stream, options)
this.mediaRecorder = mediaRecorder
this.mediaRecorder.start(100) // 每100ms收集一次数据
this.isRecording = true
} catch (err) {
console.error('录制失败:', err)
}
},
stopRecording() {
this.mediaRecorder.stop()
this.isRecording = false
this.mediaRecorder.onstop = () => {
this.videoBlob = getRecordedBlob()
this.recordedUrl = URL.createObjectURL(this.videoBlob)
// 释放媒体流
this.stream.getTracks().forEach(track => track.stop())
}
}
}
}
</script>
视频压缩主要通过以下方式实现: - 降低分辨率(如从1080p降至720p) - 减少帧率(如从30fps降至15fps) - 调整比特率(控制每秒数据量) - 使用更高效的编码格式(如H.265代替H.264)
// utils/video-compressor.js
import { createFFmpeg, fetchFile } from '@ffmpeg/ffmpeg'
export async function compressVideo(blob, options = {}) {
const ffmpeg = createFFmpeg({ log: true })
await ffmpeg.load()
const inputName = 'input.webm'
const outputName = 'output.mp4'
ffmpeg.FS('writeFile', inputName, await fetchFile(blob))
await ffmpeg.run(
'-i', inputName,
'-r', options.frameRate || '15',
'-vf', `scale=${options.width || '640'}:${options.height || '480'}`,
'-b:v', options.bitrate || '1M',
'-c:v', 'libx264',
'-preset', 'fast',
'-crf', '28',
outputName
)
const data = ffmpeg.FS('readFile', outputName)
return new Blob([data.buffer], { type: 'video/mp4' })
}
methods: {
async compressVideo() {
try {
this.isCompressing = true
const compressedBlob = await compressVideo(this.videoBlob, {
width: 640,
height: 480,
frameRate: 15,
bitrate: '500k'
})
this.compressedUrl = URL.createObjectURL(compressedBlob)
this.compressedSize = (compressedBlob.size / 1024 / 1024).toFixed(2)
// 显示压缩前后对比
const originalSize = (this.videoBlob.size / 1024 / 1024).toFixed(2)
console.log(`压缩率: ${(compressedBlob.size / this.videoBlob.size * 100).toFixed(1)}%`)
console.log(`原始大小: ${originalSize}MB → 压缩后: ${this.compressedSize}MB`)
} catch (err) {
console.error('压缩失败:', err)
} finally {
this.isCompressing = false
}
}
}
”`vue
<div v-if="isRecording" class="recording-indicator">
<span class="red-dot"></span>
录制中... {{ formatTime(recordingTime) }}
</div>
</div>
<!-- 控制按钮 -->
<div class="controls">
<button
@click="toggleRecording"
:class="{ 'recording': isRecording }"
>
{{ isRecording ? '停止录制' : '开始录制' }}
</button>
<select v-model="selectedResolution">
<option v-for="res in resolutions" :value="res">
{{ res.label }}
</option>
</select>
<button
@click="toggleCamera"
:disabled="!hasCameraAccess"
>
{{ showCamera ? '关闭摄像头' : '开启摄像头' }}
</button>
</div>
<!-- 录制结果 -->
<div v-if="recordedUrl" class="recordings">
<h3>录制结果</h3>
<div class="video-container">
<video :src="recordedUrl" controls></video>
<div class="video-info">
<p>格式: {{ videoInfo.format }}</p>
<p>大小: {{ (videoInfo.size / 1024 / 1024).toFixed(2) }}MB</p>
<p>时长: {{ formatTime(videoInfo.duration) }}</p>
</div>
</div>
<div class="compression-options">
<h4>压缩选项</h4>
<label>
分辨率:
<select v-model="compressionOptions.resolution">
<option value="original">原始</option>
<option value="720p">720p</option>
<option value="480p">480p</option>
<option value="360p">360p</option>
</select>
</label>
<label>
帧率:
<select v-model="compressionOptions.frameRate">
<option value="30">30fps</option>
<option value="24">24fps</option>
<option value="15">15fps</option>
<option value="10">10fps</option>
</select>
</label>
<button
@click="compressVideo"
:disabled="isCompressing"
>
{{ isCompressing ? '压缩中...' : '开始压缩' }}
</button>
</div>
<!-- 压缩结果 -->
<div v-if="compressedUrl" class="compressed-result">
<h4>压缩结果 ({{ compressionRatio }}% 压缩率)</h4>
<div class="video-container">
<video :src="compressedUrl" controls></video>
<div class="video-info">
<p>格式: MP4</p>
<p>大小: {{ (compressedInfo.size / 1024 / 1024).toFixed(2) }}MB</p>
<p>时长: {{ formatTime(compressedInfo.duration) }}</p>
</div>
</div>
<button @click="downloadCompressed">下载压缩视频</button>
</div>
</div>