news 2026/4/18 5:43:44

零基础通过 Vue 3 实现前端视频录制 —— 从原理到实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
零基础通过 Vue 3 实现前端视频录制 —— 从原理到实战

一、 为什么要在前端做录制?

在传统的安防或直播业务中,视频录制通常由后端流媒体服务器完成。但在某些场景下(如用户想快速保存当前看到的画面、制作简短的证据片段),前端录制具有不可替代的优势:

  • 即时性:所见即所得,无需等待服务器处理。
  • 零服务器成本:利用客户端算力,不占用服务器磁盘和带宽。
  • 灵活性:用户可以随时开始、随时停止。

二、 核心技术方案

在纯前端实现视频录制,最成熟且兼容性最好的方案是使用浏览器原生的MediaStream Recording API

1. 核心 API:MediaRecorder

你可以把它想象成浏览器内置的一个“录像机”。

  • 输入源 (Source):给它一个视频流(Stream),就像给录像机插上信号线。
  • 录制中 (Recording):它会将流数据不断地转换成二进制数据块(Chunks)。
  • 输出 (Output):当你喊“Cut”时,它将所有数据块拼接成一个完整的视频文件(Blob),供用户下载。

2. 数据源获取:captureStream

在我们的项目中,视频源来自于<video>标签播放的实时画面(包括 flv.js 解码后的画面)。我们使用HTMLMediaElement.captureStream()方法就能直接从<video>标签捕获当前播放的画面。

3. 文件格式

通常默认为WebM格式 (Chrome/Firefox),支持性最好。为了平衡画质和体积,我们优先尝试使用video/webm;codecs=vp9编码。

三、 业务逻辑设计

为了保证用户体验和程序的健壮性,在编码之前,我们需要设计好完整的业务逻辑:

1. 录制状态管理

  • 引入一个状态变量isRecording(Boolean) 来标记当前是否正在录制。
  • UI 反馈:当处于录制状态时,按钮图标应变化(如变红或显示停止图标),文字变为“停止录制”,给用户明确的反馈。

2. 交互流程

  • 点击录制按钮
    • 若未录制:初始化MediaRecorder,开始捕获流,置isRecording = true
    • 若正在录制:调用停止方法,导出文件,下载保存,置isRecording = false

3. 异常与边界处理 (关键)

  • 切换视频源时:如果用户在录制过程中切换了摄像头(即<video>src变了),必须自动停止当前录制并保存,否则流会中断或混合不同视频源的数据。
  • 页面销毁时:Vue 组件销毁 (onUnmounted) 时需要检查是否在录制,如果是,则强制停止并保存,防止内存泄漏。
  • 无视频流时:如果当前没有播放视频,点击录制应提示“请先播放视频”。

四、 具体实现步骤

第一步:核心实现useMediaRecorder.ts

它的职责单一且纯粹:只管录制,不管 UI

// useMediaRecorder.tsimport{ref,onUnmounted,unref}from'vue'importtype{Ref}from'vue'// 定义配置项接口interfaceUseMediaRecorderOptions{mimeType?:string// 视频编码格式,如 'video/webm;codecs=vp9'filenamePrefix?:string// 下载文件的前缀}exportfunctionuseMediaRecorder(// 接收一个响应式的 video 元素引用videoTarget:Ref<HTMLVideoElement|null>|HTMLVideoElement|null,options:UseMediaRecorderOptions={}){const{mimeType='video/webm;codecs=vp9',filenamePrefix='record'}=options// 响应式状态:告诉外部当前是否正在录制constisRecording=ref(false)// 内部变量:录像机实例和数据仓库letmediaRecorder:MediaRecorder|null=nullletrecordedChunks:Blob[]=[]// --- 核心动作:开始录制 ---conststartRecording=()=>{constvideoEl=unref(videoTarget)if(!videoEl)returntry{// 1. 获取“信号线”:从 video 标签捕获流// 兼容性写法:不同浏览器 API 名称可能不同conststream=(videoElasany).captureStream?(videoElasany).captureStream():(videoElasany).mozCaptureStream()if(!stream)thrownewError('无法获取视频流')// 2. 启动“录像机”// 这里可以做一些兼容性检查,如果不支持 VP9 就降级到普通 WebMmediaRecorder=newMediaRecorder(stream,{mimeType})// 3. 收集数据:每当有数据产生,就存入仓库mediaRecorder.ondataavailable=(event)=>{if(event.data&&event.data.size>0){recordedChunks.push(event.data)}}// 4. 停止时的处理:打包并下载mediaRecorder.onstop=()=>{// 将所有碎片数据(Chunks)合并为一个大文件(Blob)constblob=newBlob(recordedChunks,{type:mimeType})// 创建下载链接consturl=URL.createObjectURL(blob)consta=document.createElement('a')a.href=url a.download=`${filenamePrefix}_${Date.now()}.webm`a.click()// 触发下载window.URL.revokeObjectURL(url)// 释放内存// 清空仓库,为下次录制做准备recordedChunks=[]mediaRecorder=null}// 5. 正式开机mediaRecorder.start()isRecording.value=trueconsole.log('开始录制视频')}catch(e){console.error('录制启动失败:',e)console.error('录制失败,浏览器可能不支持')}}// --- 核心动作:停止录制 ---conststopRecording=()=>{if(mediaRecorder&&mediaRecorder.state!=='inactive'){mediaRecorder.stop()// 这会触发上面的 onstop 事件isRecording.value=falseconsole.log('录制已停止,正在下载...')}}// --- 自动护航:生命周期管理 ---// 如果组件被销毁了(用户切走了页面),录制会自动停止onUnmounted(()=>{if(isRecording.value){stopRecording()}})// 暴露出外部需要的方法和状态return{isRecording,startRecording,stopRecording}}

第二步:在组件中使用

<!-- main.vue --> <script setup lang="ts"> import { ref } from 'vue' import { useMediaRecorder } from '@renderer/composables/useMediaRecorder' // 1. 获取 video 标签的引用 const videoPlayerRef = ref<HTMLVideoElement | null>(null) // 2. 引入录制功能 const { isRecording, // 当前是不是在录制 startRecording, // 开始方法 stopRecording // 停止方法 } = useMediaRecorder(videoPlayerRef) // 3. 按钮点击处理逻辑 const handleRecordClick = () => { if (isRecording.value) { stopRecording() } else { startRecording() } } </script> <template> <!-- 绑定 ref --> <video ref="videoPlayerRef" ... ></video> <!-- 按钮样式随状态自动变化 --> <button @click="handleRecordClick" :class="{ 'red-btn': isRecording }" > {{ isRecording ? '停止录制' : '开始录制' }} </button> </template>

五、 新手避坑指南

在实现过程中,有几个坑需要特别注意:

  1. MIME Type 兼容性

    • 并不是所有浏览器都支持video/webm;codecs=vp9
    • 解决方案:在代码中添加MediaRecorder.isTypeSupported()检查,如果不支持高清格式,自动降级为普通video/webm
  2. 切换视频源

    • 当用户在录制过程中切换了摄像头,旧的流(Stream)会失效。
    • 解决方案:在组件的watch中监听视频源变化,如果正在录制,强制调用stopRecording()保存当前片段。
  3. 内存泄漏

    • 生成的BlobURL (URL.createObjectURL) 会占用内存。
    • 解决方案:下载触发后,务必调用URL.revokeObjectURL(url)释放内存。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/5 2:17:09

空间计算、空间智能何时普及,需要做什么准备

一、这些技术大概什么时候“真正普及”&#xff1f; 这里说的普及&#xff0c;不是“有新闻、有展会”&#xff0c;而是&#xff1a; 有稳定的硬件出货量有持续付费的商业场景普通开发者可以靠它挣钱或找工作 综合空间计算/AR/VR/MR、3DGS、空间智能、AI Agent 等信息&#…

作者头像 李华
网站建设 2026/4/16 17:10:07

面试必看:递增的三元子序列

LeetCode 334. 递增的三元子序列 题解 题目描述 给定一个整数数组 nums&#xff0c;判断数组中是否存在下标满足 i < j < k 的三元子序列&#xff0c;使得 nums[i] < nums[j] < nums[k]。若存在满足条件的三元组&#xff0c;返回 true&#xff0c;否则返回 false。…

作者头像 李华
网站建设 2026/4/18 5:20:41

2026年软件测试公众号热度最高内容深度解析与专业行动指南

随着AI技术的普及和行业标准更新&#xff0c;2026年软件测试公众号内容热度呈现“专业化场景化”特征&#xff0c;阅读量和分享率成为核心指标&#xff0c;算法推荐贡献超50%的流量。从业者对效率提升和职业发展的迫切需求&#xff0c;推动内容向深度、实操性转型&#xff0c;A…

作者头像 李华
网站建设 2026/4/16 20:19:36

住宅代理与数据中心代理在爬虫中的选择

在网络爬虫与数据采集场景中&#xff0c;代理 IP 是突破访问限制、隐藏真实身份、保障采集稳定性的核心组件。其中住宅代理与数据中心代理是最主流的两类方案&#xff0c;二者在来源属性、匿名等级、访问效果、成本与适用场景上存在显著差异。选择不当会直接导致 IP 封禁、采集…

作者头像 李华