# 如何使用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>