尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

微信小程序录制视频功能实现:三条路线与避坑指南

微信小程序录制视频功能实现:三条路线与避坑指南 微信小程序录制视频功能实现看起来只是调一个 API 那么简单实际做过的都知道坑都在权限、原生组件层级和机型差异里。我最近刚好在给一个校园跑腿服务平台做“任务凭证视频上传”模块顺手也把婚礼邀请函小程序里的留言视频、客服工单的视频反馈场景都过了一遍发现不同业务对“录制视频”的诉求差别很大有的只要 5 秒确认视频有的要连续录制 60 秒讲解有的还想在画面上叠贴纸和文字水印。如果你正准备在微信小程序里做录制视频功能不管你是刚接手项目的开发还是需要评估技术方案的产品这篇内容都会把三条主流路线拆开讲清楚并给出可直接抄作业的代码和排查表。1. 先搞清楚需求边界小程序里到底能录什么1.1 三种视频来源与适用场景对比很多人一上来就问“微信小程序录制视频功能实现用哪个 API”但真正该先问的是你要的是“调用系统相机拍一段”还是“在小程序页面里自己控制摄像头录一段”还是“把 Canvas 动画合成一段视频”这三件事的底层能力完全不同选错了后面全是坑。我把常见的三种路线整理成了一张表你可以直接对照自己的业务场景选方案核心 API适用场景优点限制系统相机/相册wx.chooseMedia、wx.chooseVideo用户拍完就传不需要自定义界面开发成本最低兼容性最好无法控制拍摄界面不能加自定义水印、贴纸camera 组件自录camera组件 CameraContext.startRecord打卡、作业提交、客服视频工单可自定义拍摄按钮、时长、前后摄像头原生组件层级高样式受限部分机型有兼容问题Canvas MediaRecorderwx.createMediaRecorder生成合成视频、动画视频、带贴纸/文字的画面画面完全由前端控制可做滤镜、贴纸、字幕需要处理音画同步基础库要求较高如果你的业务只是“让用户上传一段视频”我建议优先用wx.chooseMedia它的稳定性和审核通过率都更高。真正需要在小程序内控制拍摄流程时再考虑camera组件。至于MediaRecorder它更适合“画面不是真实摄像头而是 Canvas 渲染内容”的场景比如生成一张动态海报视频、把用户上传的图片合成一段短视频。提示不要为了“看起来更专业”而强行用 camera 组件自录。系统相机方案在弱网、低端机、权限异常时的表现通常更稳能少写很多兼容代码。1.2 权限、基础库与设备兼容性预判不管选哪条路线权限都是第一道门槛。微信小程序里跟录制视频相关的权限主要有两个scope.camera摄像头权限camera 组件和系统相机都会用到。scope.record麦克风权限如果视频需要收音就必须申请。另外保存到相册还需要scope.writePhotosAlbum。这三个权限不是一次授权就永久有效用户可以在小程序设置页里随时关掉所以每次进入录制页前最好做一次权限预检。基础库版本方面camera 组件从 2.10.0 开始支持wx.createMediaRecorder从 2.11.0 开始支持。你可以在app.json里设置requiredBackgroundModes但更关键的是在project.config.json或小程序后台设置最低基础库版本。如果你用的是 uniapp 打包微信小程序还要注意 uniapp 编译后的基础库版本可能和原生写法有差异建议在manifest.json里锁定微信小程序的基础库范围避免低版本用户进来直接白屏。设备兼容性上iOS 和 Android 的差异非常明显。iOS 对摄像头方向、视频旋转角度的处理比较“自动”有时候你录出来在相册看是正的上传到后端用播放器打开却转了 90 度Android 则更容易出现分辨率不统一、帧率不稳定、部分机型前置摄像头镜像翻转的问题。我的经验是录制参数不要写死先探测设备能力再给一个保守的默认值。比如优先用 720P、24fps遇到低端机自动降到 480P、15fps。注意开发者工具里的 camera 组件通常只能看到占位图无法真正预览摄像头画面。录制视频功能必须真机调试尤其是权限弹窗、前后摄像头切换、录制时长这些环节。2. 用 camera 组件实现一个可用的录制页2.1 页面结构与 camera 组件参数配置先看一个最小可用的录制页结构。假设我们要做一个“按住录制、松开停止”的交互页面顶部显示计时底部是录制按钮和切换摄像头按钮。WXML 大致如下view classrecord-page camera device-position{{devicePosition}} flash{{flash}} binderroronCameraError stylewidth: 100%; height: 70vh; cover-view classtimer{{formattedTime}}/cover-view /camera view classtoolbar button bindtapswitchCamera切换摄像头/button button bindtouchstartstartRecord bindtouchendstopRecord classrecord-btn 按住录制 /button button bindtapchooseFromAlbum从相册选择/button /view /view这里有几个关键点。第一camera 是原生组件层级最高普通 view 盖不住它所以计时器、按钮如果要在摄像头画面上方显示必须用cover-view。第二device-position只接受front和back切换时直接 setData 即可。第三binderror一定要接摄像头被占用、权限被拒、硬件异常都会走这里。样式上camera 的宽高建议用 vh 或固定像素不要用百分比嵌套太深。如果你用了自定义导航栏还要注意微信小程序顶部导航栏高度在不同机型上不一样可以用wx.getSystemInfoSync()拿到statusBarHeight和safeArea给页面顶部留出安全距离避免录制按钮被刘海屏遮挡。2.2 录制逻辑与 CameraContext 调用页面 JS 的核心是拿到CameraContext然后调用startRecord和stopRecord。代码可以这样写Page({ data: { devicePosition: back, flash: off, recording: false, formattedTime: 00:00, tempVideoPath: , tempThumbPath: }, onReady() { this.ctx wx.createCameraContext() this.timer null this.seconds 0 }, onCameraError(e) { console.error(camera error, e.detail) wx.showToast({ title: 摄像头不可用, icon: none }) }, switchCamera() { this.setData({ devicePosition: this.data.devicePosition back ? front : back }) }, startRecord() { if (this.data.recording) return this.seconds 0 this.setData({ recording: true, formattedTime: 00:00 }) this.ctx.startRecord({ success: () { this.timer setInterval(() { this.seconds 1 this.setData({ formattedTime: this.formatTime(this.seconds) }) // 超过 30 秒自动停止具体上限以官方文档为准 if (this.seconds 30) { this.stopRecord() } }, 1000) }, fail: (err) { console.error(startRecord fail, err) this.setData({ recording: false }) wx.showToast({ title: 录制启动失败, icon: none }) } }) }, stopRecord() { if (!this.data.recording) return clearInterval(this.timer) this.ctx.stopRecord({ success: (res) { this.setData({ recording: false, tempVideoPath: res.tempVideoPath, tempThumbPath: res.tempThumbPath }) wx.previewMedia({ sources: [{ url: res.tempVideoPath, type: video }] }) }, fail: (err) { console.error(stopRecord fail, err) this.setData({ recording: false }) } }) }, formatTime(sec) { const m String(Math.floor(sec / 60)).padStart(2, 0) const s String(sec % 60).padStart(2, 0) return ${m}:${s} } })这段代码里有几个容易踩坑的细节。startRecord的成功回调只代表录制已经开始不代表文件已经生成真正拿到视频路径是在stopRecord的 success 回调里返回tempVideoPath和tempThumbPath。这两个路径都是临时文件路径小程序重启后可能失效所以录制完成后要尽快上传或转存。另外录制时长不要依赖单一 API 限制。官方对单次录制有时长约束但不同基础库版本表现不完全一致。更稳妥的做法是自己用setInterval计时到点主动调用stopRecord。上面代码里我写了 30 秒自动停止你可以根据业务改成 10 秒、15 秒或 60 秒但建议不要无限制录制否则内存和文件体积都会失控。提示startRecord和stopRecord必须成对调用。如果用户快速点击、页面隐藏、小程序切后台可能会出现“录制中”状态没清掉的情况。建议在onHide和onUnload里都检查一次必要时强制停止并清理定时器。2.3 预览、回放与保存到相册录制完成后通常要给用户一个预览确认的机会。可以用wx.previewMedia直接全屏预览也可以在当前页面嵌入video组件回放。如果用户确认要保存到相册再调用wx.saveVideoToPhotosAlbumsaveToAlbum() { const { tempVideoPath } this.data if (!tempVideoPath) return wx.saveVideoToPhotosAlbum({ filePath: tempVideoPath, success: () wx.showToast({ title: 已保存到相册 }), fail: (err) { if (err.errMsg.includes(auth deny)) { wx.showModal({ title: 需要相册权限, content: 请在设置中开启保存到相册权限, success: (res) { if (res.confirm) wx.openSetting() } }) } } }) }这里要注意saveVideoToPhotosAlbum在 iOS 上可能会触发“仅添加照片”权限用户如果选了“不允许”或“仅选中的照片”保存会失败。不要只弹一个 toast 就完事最好引导用户去wx.openSetting里打开。如果业务不需要保存到相册这一步完全可以省略直接上传到后端更省事。3. 更灵活的自定义录制Canvas MediaRecorder 方案3.1 什么时候需要 MediaRecordercamera 组件方案适合“真实拍摄”但它有一个硬伤你很难在录制的视频上叠加动态贴纸、文字水印、滤镜或者自定义动画。因为 camera 是原生组件普通 canvas 画上去也盖不住它而 cover-view 只能放简单文本和图片做不了复杂合成。这时候wx.createMediaRecorder就派上用场了。它可以把一个 WebGL Canvas 的内容录制成视频文件。你可以先把摄像头画面、图片、文字、动画全部画到 Canvas 上再用 MediaRecorder 录下来。典型场景包括生成带用户昵称和头像的祝福视频、把多张照片合成一段幻灯片视频、给视频加上实时时间水印、做简单的动画短片。不过要提前说清楚MediaRecorder 的音频采集能力在不同基础库版本上差异较大如果你需要录麦克风声音我更建议用 camera 组件方案或者用wx.getRecorderManager()单独录一份音频上传到服务端后再用后端工具合流。不要在前端硬扛音画同步很容易出现声音比画面快半秒、慢半秒的问题。3.2 初始化与参数配置使用 MediaRecorder 之前页面上要有一个typewebgl的 canvas。WXML 如下canvas typewebgl idrecordCanvas stylewidth: 300px; height: 400px; /canvas button bindtapstartMediaRecord开始合成录制/button button bindtapstopMediaRecord停止并导出/buttonJS 里先拿到 canvas 节点再创建 MediaRecorderPage({ data: { recorder: null }, onReady() { const query wx.createSelectorQuery() query.select(#recordCanvas) .fields({ node: true, size: true }) .exec((res) { const canvas res[0].node const ctx canvas.getContext(webgl) // 这里可以开始绘制你的画面 this.canvas canvas this.gl ctx }) }, startMediaRecord() { const recorder wx.createMediaRecorder(this.canvas, { width: 720, height: 1280, fps: 24, videoBitsPerSecond: 2500000, audioBitsPerSecond: 128000 }) this.setData({ recorder }) recorder.start() }, stopMediaRecord() { const { recorder } this.data if (!recorder) return recorder.stop() recorder.on(stop, (res) { console.log(导出视频临时路径, res.tempFilePath) wx.previewMedia({ sources: [{ url: res.tempFilePath, type: video }] }) }) } })参数上width和height决定输出视频分辨率建议按 9:16 竖屏或 16:9 横屏设置不要超过 1080P否则低端机容易卡顿。fps一般 24 或 30 就够videoBitsPerSecond是码率720P 用 2.5Mbps 左右1080P 用 4Mbps 到 6Mbps。码率越高画质越好但文件体积也越大上传时间更长。你可以用“码率 × 时长 ÷ 8”估算文件大小比如 2.5Mbps 录 30 秒大约 9.4MB上传前心里有数。注意MediaRecorder 依赖 WebGL Canvas部分低端 Android 机可能不支持 WebGL 或性能不足。上线前一定要在低端机真机上跑一遍观察录制过程中是否掉帧、发热、闪退。3.3 开始、暂停、停止与导出MediaRecorder 的生命周期通常包括start、pause、resume、stop。如果你做的是“分段录制”或者“暂停后继续”可以这样控制pauseRecord() { const { recorder } this.data if (recorder) recorder.pause() }, resumeRecord() { const { recorder } this.data if (recorder) recorder.resume() }导出视频时stop事件返回的tempFilePath同样是临时路径。如果你希望文件在小程序内长期可用可以把它复制到wx.env.USER_DATA_PATH目录下。wx.env.USER_DATA_PATH是小程序的用户文件目录适合保存附件、草稿、缓存文件。例如const fs wx.getFileSystemManager() const target ${wx.env.USER_DATA_PATH}/record_${Date.now()}.mp4 fs.copyFileSync(res.tempFilePath, target) console.log(持久化路径, target)这样做的好处是用户下次进入小程序时还能找到草稿文件坏处是占用用户存储空间需要定期清理。我的做法是录制完成后先上传上传成功就删本地临时文件上传失败才转存到USER_DATA_PATH并给用户一个“重新上传”的入口。3.4 音画同步与常见坑MediaRecorder 最让人头疼的是音画同步。因为 Canvas 绘制和音频播放是两个独立的时钟如果绘制帧率不稳定或者音频播放被卡顿打断导出后就会出现声音和画面不同步。我试过几种方案最后总结出三条经验第一画面尽量简单。不要在 Canvas 里做太复杂的逐帧动画尤其是大量阴影、模糊、粒子效果低端机渲染不过来录制出来会卡成 PPT。第二音频不要依赖前端实时采集。如果必须要有声音可以用wx.getRecorderManager()录一份独立的音频文件上传到服务端后合成。前端只负责画面后端用转码工具把音频和视频合到一起稳定性高很多。第三录制时长控制在 15 秒以内。Canvas 录制时间越长内存占用越高导出失败的概率也越大。如果是长视频需求建议引导用户用系统相机拍摄而不是在前端合成。4. 录制后的文件处理上传、压缩、转存与回显4.1 临时文件生命周期与 wx.env.USER_DATA_PATH不管用哪种方案录制得到的都是临时文件。临时文件的生命周期很短小程序冷启动后可能就找不到了。所以录制完成后你的第一优先级是“上传到后端”而不是“先存起来以后再说”。如果网络不好或者用户主动取消上传你可以把文件转存到wx.env.USER_DATA_PATH。这个目录是小程序专属的用户文件目录读写不需要额外权限但容量有限官方建议不要超过 200MB。保存附件时建议用时间戳加随机数命名避免覆盖const fs wx.getFileSystemManager() const fileName video_${Date.now()}_${Math.floor(Math.random() * 1000)}.mp4 const filePath ${wx.env.USER_DATA_PATH}/${fileName} fs.saveFileSync(tempFilePath, filePath)需要注意的是wx.env.USER_DATA_PATH里的文件不会自动同步到云端用户换设备后就看不到了。如果你做的是校园跑腿服务平台的视频凭证正确做法是上传成功后由后端返回一个永久 URL前端只缓存 URL不缓存视频文件本身。4.2 上传到后端与进度、失败重试小程序上传视频用wx.uploadFile它支持formData、header 和进度监听。一个带进度和重试的上传函数可以这样写function uploadVideo(filePath, onProgress) { return new Promise((resolve, reject) { const task wx.uploadFile({ url: https://your-domain.com/api/upload/video, filePath, name: file, header: { content-type: multipart/form-data }, formData: { bizType: task_proof, duration: 30 }, success: (res) { if (res.statusCode 200) { resolve(JSON.parse(res.data)) } else { reject(new Error(上传失败${res.statusCode})) } }, fail: reject }) task.onProgressUpdate((res) { if (onProgress) onProgress(res.progress) }) }) }这里有几个排查点。第一上传域名必须在小程序后台的“服务器域名”里配置且必须是 HTTPS。第二真机调试时如果请求无法到达后端优先检查手机和电脑是否在同一局域网、后端是否监听 0.0.0.0、防火墙是否放行端口。第三如果用了反向代理注意Content-Length和超时时间视频文件较大时容易 502。第四iOS 上后台切换会导致上传中断建议上传时提示用户不要退出当前页面。提示视频上传不要直接传原始文件。30 秒 1080P 视频可能有三四十兆弱网下上传成功率很低。先压缩再上传用户体验会好很多。4.3 视频压缩与格式兼容微信小程序提供了wx.compressVideo可以对视频做压缩。常用参数包括quality、bitrate、fps、resolution。例如wx.compressVideo({ src: tempVideoPath, quality: medium, bitrate: 1500, fps: 24, resolution: 0.7, success: (res) { console.log(压缩后路径, res.tempFilePath) console.log(压缩前大小, res.size) }, fail: (err) { console.error(压缩失败, err) } })quality可选low、medium、highresolution是分辨率缩放比例0.7 表示按原尺寸的 70% 输出。实测下来把 1080P 压到 720P、码率 1.5Mbps文件体积能减少 60% 以上画质在手机上看几乎没区别。如果compressVideo在你目标基础库上不支持可以退回到服务端转码前端只负责上传原始文件。格式兼容方面小程序录制出来的视频一般是 MP4 封装H.264 编码兼容性最好。但如果用户从相册选择的是 MOV、AVI 等格式部分 Android 播放器可能无法直接播放。稳妥的做法是前端预览时用小程序video组件上传后由后端统一转成 MP4/H.264再返回给前端回显。5. 常见问题与排查技巧实录5.1 权限、黑屏、录制失败速查表录制视频功能最容易出问题的就是权限和原生组件。我把实际项目中遇到的高频问题整理成了下面这张表你可以直接对照排查现象可能原因解决思路页面黑屏没有摄像头画面权限未授权、摄像头被其他应用占用、开发者工具不支持真机调试检查scope.camera提示用户关闭其他相机应用点击录制无反应CameraContext未创建、startRecord失败未捕获在onReady里创建给 fail 回调加 toast 和日志录出来没有声音未申请scope.record、设备麦克风被占用录制前调用wx.authorize申请麦克风权限视频方向不对iOS/Android 旋转角度处理不同上传后服务端读取旋转元数据并转正或统一用竖屏录制录制超过一定时长自动停止平台单次录制时长限制自己计时到点主动stopRecord引导用户分段录制保存到相册失败相册权限被拒、iOS 仅添加照片权限引导wx.openSetting或改为直接上传后端真机上传失败域名未配置、HTTPS 证书异常、局域网不通检查服务器域名白名单用真机日志看请求错误码MediaRecorder 导出失败WebGL 不支持、内存不足、时长过长降分辨率、缩短录制时长、加错误兜底这张表里的每一条我基本都踩过。尤其是“视频方向不对”一开始我以为是前端录制参数问题后来发现是后端转码时没有读取视频的旋转元数据播放器默认按 0 度渲染画面就躺下了。后来统一在后端用转码工具做一次 autorotate问题才彻底解决。5.2 真机与开发者工具差异开发者工具可以帮你调页面结构、按钮交互、上传逻辑但摄像头预览、录制、麦克风采集这些能力必须真机验证。我的建议是准备三台测试机一台 iOS 中高端、一台 Android 中高端、一台低端 Android。低端机重点看录制过程中是否卡顿、发热、闪退iOS 重点看权限弹窗和视频方向Android 重点看分辨率和帧率。另外如果你用的是 uniapp 打包微信小程序真机调试时可能会遇到“请求无法到达后端”的问题。这种情况优先检查 uniapp 的manifest.json里是否配置了合法的请求域名以及是否开启了“不校验合法域名”的调试模式。上线前一定要关掉这个模式否则审核会出问题。5.3 审核与隐私合规注意事项小程序涉及摄像头、麦克风、相册权限时审核会比普通工具类严格。你需要在《用户隐私保护指引》里明确说明收集这些权限的用途比如“用于录制任务凭证视频”“用于上传视频反馈”。如果业务涉及社交、视频发布可能还需要对应的类目资质。另外录制页面不要默认开启摄像头。用户进入页面后先展示一个说明页告诉用户“接下来将使用摄像头录制视频”用户点击“开始录制”后再初始化 camera 组件。这样既符合最小必要原则也能减少用户对隐私的顾虑。如果业务涉及未成年人还要注意监护人同意流程这里不展开但产品设计时一定要考虑。6. 我在项目里沉淀的几条实操经验第一条能不用 camera 组件就不用。系统相机wx.chooseMedia的稳定性、兼容性、审核通过率都更高。只有当业务明确要求“在小程序页面内控制拍摄流程”时才值得上 camera 组件。录制视频功能实现的第一步不是写代码而是判断“这个功能真的需要自录吗”。第二条录制前先做一次“设备能力探测”。我会在onLoad里检查基础库版本、是否支持 camera、是否支持 MediaRecorder、当前设备信息然后决定走哪条路线。如果基础库太低直接降级到wx.chooseMedia不要硬上。第三条录制时长和分辨率要设上限。我见过有项目允许用户录 5 分钟结果低端机直接内存溢出闪退。我的默认配置是camera 自录最长 30 秒720P24fpsMediaRecorder 合成最长 15 秒720P24fps。超过这个范围就提示用户分段录制或改用系统相机。第四条错误处理要覆盖到“每一个 fail”。录制视频涉及权限、硬件、网络、文件系统任何一个环节都可能失败。不要只写 success 回调fail和complete里都要有清理逻辑和用户提示。我习惯在onUnload里统一清理定时器、停止录制、释放 CameraContext避免页面退出后还在后台占用摄像头。最后再分享一个小技巧如果你要做的是“婚礼邀请函小程序”里的宾客留言视频或者“校园跑腿服务平台”里的任务凭证视频录制页最好加一个“重录”按钮并且把“上传中”“上传失败”“上传成功”三个状态区分清楚。用户录完视频后最怕的就是“点了上传没反应”给足状态反馈比任何技术优化都更能提升体验。
返回列表