UniApp跨端语音输入功能开发指南:微信小程序与H5全场景实现

一、语音输入功能技术选型分析

1.1 平台原生能力对比

微信小程序提供wx.getRecorderManagerwx.startRecord两种录音API,前者支持更精细的控制(如采样率、编码格式),后者为简化版。H5端则依赖浏览器原生MediaRecorder API或WebRTC技术,但存在浏览器兼容性问题(如Safari对部分编码格式的支持缺失)。

1.2 跨端适配方案选择

方案一:条件编译+平台判断。通过#ifdef MP-WEIXIN等预编译指令区分代码逻辑,适合功能差异较大的场景。方案二:封装适配器层,将平台差异抽象为统一接口,提升代码可维护性。推荐采用方案二,示例结构如下:

  1. // adapter/voice.js
  2. export default {
  3. startRecord(options) {
  4. if (uni.getSystemInfoSync().platform === 'mp-weixin') {
  5. return wxStartRecord(options) // 微信小程序实现
  6. } else {
  7. return h5StartRecord(options) // H5实现
  8. }
  9. }
  10. }

二、微信小程序端实现细节

2.1 录音权限管理

manifest.json中配置"requiredPrivateInfos": ["getRecorderManager"],并在页面加载时动态申请权限:

  1. uni.authorize({
  2. scope: 'scope.record',
  3. success() { console.log('授权成功') },
  4. fail() { uni.showModal({ title: '需要录音权限', content: '请在设置中开启' }) }
  5. })

2.2 完整录音流程

  1. // 初始化录音管理器
  2. const recorderManager = uni.getRecorderManager()
  3. recorderManager.onStart(() => { console.log('录音开始') })
  4. recorderManager.onStop((res) => {
  5. const { tempFilePath, duration } = res
  6. // 处理录音文件
  7. })
  8. // 开始录音(微信特有参数)
  9. recorderManager.start({
  10. format: 'mp3',
  11. sampleRate: 16000,
  12. encodeBitRate: 128000,
  13. duration: 60000 // 最大时长
  14. })
  15. // 停止录音
  16. setTimeout(() => recorderManager.stop(), 5000)

三、H5端实现方案

3.1 浏览器兼容性处理

通过MediaRecorder.isTypeSupported()检测编码格式支持情况:

  1. const isSupported = MediaRecorder.isTypeSupported('audio/webm;codecs=opus')
  2. const options = isSupported
  3. ? { mimeType: 'audio/webm;codecs=opus' }
  4. : { mimeType: 'audio/wav' } // 降级方案

3.2 完整实现示例

  1. function startH5Record() {
  2. return new Promise((resolve, reject) => {
  3. navigator.mediaDevices.getUserMedia({ audio: true })
  4. .then(stream => {
  5. const mediaRecorder = new MediaRecorder(stream, options)
  6. const chunks = []
  7. mediaRecorder.ondataavailable = e => chunks.push(e.data)
  8. mediaRecorder.onstop = () => {
  9. const blob = new Blob(chunks, { type: options.mimeType })
  10. const audioUrl = URL.createObjectURL(blob)
  11. resolve({ audioUrl, duration: Date.now() - startTime })
  12. stream.getTracks().forEach(track => track.stop())
  13. }
  14. mediaRecorder.start(100) // 每100ms收集一次数据
  15. setTimeout(() => mediaRecorder.stop(), 5000)
  16. })
  17. .catch(err => reject(new Error('麦克风访问失败')))
  18. })
  19. }

四、跨端统一封装实践

4.1 适配器层设计

  1. // voice-adapter.js
  2. const platformMap = {
  3. mpWeixin: {
  4. start: (options) => wxStartRecord(options),
  5. stop: (cb) => wxStopRecord(cb)
  6. },
  7. h5: {
  8. start: (options) => h5StartRecord(options),
  9. stop: (cb) => h5StopRecord(cb)
  10. }
  11. }
  12. export function createVoiceAdapter(platform) {
  13. const platformImpl = platformMap[platform] || platformMap.h5
  14. return {
  15. startRecord(options) {
  16. return platformImpl.start(options)
  17. },
  18. stopRecord(callback) {
  19. return platformImpl.stop(callback)
  20. }
  21. }
  22. }

4.2 页面组件实现

  1. <template>
  2. <view>
  3. <button @click="startRecording">开始录音</button>
  4. <button @click="stopRecording" :disabled="!isRecording">停止录音</button>
  5. <audio v-if="audioUrl" :src="audioUrl" controls></audio>
  6. </view>
  7. </template>
  8. <script>
  9. import { createVoiceAdapter } from '@/adapter/voice'
  10. export default {
  11. data() {
  12. return {
  13. isRecording: false,
  14. audioUrl: '',
  15. adapter: null
  16. }
  17. },
  18. created() {
  19. const platform = uni.getSystemInfoSync().platform
  20. this.adapter = createVoiceAdapter(platform === 'mp-weixin' ? 'mpWeixin' : 'h5')
  21. },
  22. methods: {
  23. async startRecording() {
  24. try {
  25. await uni.authorize({ scope: 'scope.record' })
  26. this.isRecording = true
  27. const result = await this.adapter.startRecord({
  28. duration: 60000,
  29. format: 'mp3'
  30. })
  31. // 处理录音结果
  32. } catch (error) {
  33. uni.showToast({ title: error.message, icon: 'none' })
  34. }
  35. },
  36. stopRecording() {
  37. this.adapter.stopRecord((res) => {
  38. this.audioUrl = res.tempFilePath || res.audioUrl
  39. this.isRecording = false
  40. })
  41. }
  42. }
  43. }
  44. </script>

五、性能优化与异常处理

5.1 内存管理策略

  • 微信小程序端:及时调用uni.saveFile将临时文件持久化,避免占用过多内存
  • H5端:使用URL.revokeObjectURL()释放对象URL
    1. // H5端资源释放示例
    2. const blobUrl = URL.createObjectURL(blob)
    3. // 使用完毕后
    4. setTimeout(() => URL.revokeObjectURL(blobUrl), 1000)

5.2 常见错误处理

错误类型 微信小程序 H5端 解决方案
权限拒绝 errMsg: "authorize:fail" NotAllowedError 引导用户开启权限
设备占用 errCode: 201 OverconstrainedError 提示用户关闭其他录音应用
超时错误 duration参数设置不当 MediaRecorder.stop()未调用 增加超时保护机制

六、进阶功能扩展

6.1 实时语音转文字

结合微信小程序wx.getRealtimeLogManager和H5的Web Speech API实现:

  1. // H5端语音识别示例
  2. const recognition = new (window.SpeechRecognition ||
  3. window.webkitSpeechRecognition ||
  4. window.mozSpeechRecognition ||
  5. window.msSpeechRecognition)()
  6. recognition.onresult = (event) => {
  7. const transcript = event.results[0][0].transcript
  8. console.log('识别结果:', transcript)
  9. }
  10. recognition.start()

6.2 录音波形可视化

使用<canvas>绘制音频波形:

  1. function drawWaveform(canvasId, audioData) {
  2. const ctx = uni.createCanvasContext(canvasId, this)
  3. const width = 300
  4. const height = 100
  5. const step = Math.ceil(audioData.length / width)
  6. ctx.beginPath()
  7. for (let i = 0; i < width; i++) {
  8. const value = audioData[i * step] / 128
  9. ctx.lineTo(i, height / 2 - value * height / 2)
  10. }
  11. ctx.stroke()
  12. ctx.draw()
  13. }

七、测试与发布注意事项

  1. 真机测试:模拟器无法完全模拟麦克风权限和硬件差异
  2. 性能测试:连续录音30分钟以上检查内存泄漏
  3. 兼容性测试:覆盖iOS/Android不同版本、微信基础库版本
  4. 审核要点
    • 微信小程序需在app.json中声明录音权限
    • H5端需处理HTTPS环境限制
    • 提供明确的隐私政策说明语音数据处理方式

通过以上方案,开发者可以在UniApp中构建出兼容微信小程序和H5的高质量语音输入功能。实际开发中建议采用渐进式增强策略,先实现基础录音功能,再逐步添加波形显示、实时转写等高级特性。对于复杂场景,可考虑使用成熟的语音SDK(如腾讯云、阿里云等提供的服务),但需注意本文要求避免提及具体厂商技术支持。