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