微信小程序录音和播放

Table of Contents

最近有个需求,微信小程序录音并播放。

录音

  • 首先,获取全局录音管理器。
  • 然后 onLoad 里面配置各种事件监听。
  • 调用录音/停止 api,处理录音事件。
// 录音管理器
const recordManager = wx.getRecorderManager()

Page({
  data: {
    voiceReportInfo: '', // 工单详情语音 url
    voiceSeconds: 0, // 语音时间
    recordStatus: '', // ready, recording
    recordConfig: {
      // 录音配置
      duration: 600000,
      sampleRate: 44100,
      numberOfChannels: 1,
      encodeBitRate: 192000,
      frameSize: 50,
      format: 'aac',
    },
  },
  onLoad: function (options) {
    recordManager.onStart((e) => this.handleRecordStart(e))
    recordManager.onStop((e) => this.handleRecordStop(e))
    recordManager.onError((e) => this.handleRecordError(e))
  },

  /**
   * 开始录音
   */
  startRecord() {
    console.log('touchStart')
    const { recordStatus } = this.data
    if (recordStatus === 'recording') return

    if (!recordStatus) {
      const _self = this
      wx.getSetting({
        success(res) {
          if (!res.authSetting['scope.record']) {
            wx.authorize({
              scope: 'scope.record',
              success() {
                _self.setData({
                  recordStatus: 'ready',
                })
                console.log('ready')
                recordManager.start(_self.data.recordConfig)
              },
              fail() {
                wx.showToast({
                  title: '未授权',
                  icon: 'none',
                })
              },
            })
          } else {
            _self.setData({
              recordStatus: 'ready',
            })
            console.log('ready')
            recordManager.start(_self.data.recordConfig)
          }
        },
        fail() {
          wx.showToast({
            title: '抱歉,目前无法录音',
            icon: 'none',
          })
        },
      })
    } else {
      this.setData({
        recordStatus: 'ready',
      })
      console.log('ready')
      recordManager.start(this.data.recordConfig)
    }
  },

  /**
   * 结束录音
   */
  endRecord() {
    console.log('touchEnd')
    const { recordStatus } = this.data
    if (recordStatus === 'ready' || !recordStatus) return

    recordManager.stop()
  },

  /**
   * 开始录音事件
   */
  handleRecordStart() {
    console.log('start')
    this.setData({
      recordStatus: 'recording',
    })
  },

  /**
   * 结束录音事件
   */
  handleRecordStop(e) {
    const { tempFilePath, duration } = e
    if (tempFilePath) {

      // 把音频上传到云服务器
      uploadFile({
        url: config.uploadUrl,
        filePath: tempFilePath,
      })
        .then((res) => {
          if (res[0].success && res[0].result) {
            this.setData({
              voiceReportInfo: res[0].result,
              voiceSeconds: Math.round(duration / 1000),
            })
          } else {
            wx.showToast({
              title: '上传失败',
              icon: 'none',
            })
          }
          this.setData({
            recordStatus: 'ready',
          })
        })
        .catch((e) => {
          wx.showToast({
            title: e.message || '上传失败',
            icon: 'none',
          })
          this.setData({
            recordStatus: 'ready',
          })
        })
    }
  },

  /**
   * 录音出错
   */
  handleRecordError(e) {
    console.log(e)
  },
})

播放音频

为了方便复用,播放音频封装成了组件。内部处理和在页面里写差不多。

  • 创建音频上下文。
  • 监听音频地址,监听相关事件。
  • 播放/暂停处理。
// components/audio-player/index.js
// 音频上下文
const innerAudioContext = wx.createInnerAudioContext()

Component({
  /**
   * 组件的属性列表
   */
  properties: {
    src: String,
    duration: Number,
  },

  /**
   * 组件的初始数据
   */
  data: {
    isPlaying: false,
  },

  observers: {
    src: function (value) {
      if (typeof value === 'string' && value) {
        innerAudioContext.src = value
        // innerAudioContext.onCanplay((e) => this.handleAudioCanplay(e))
        innerAudioContext.onPlay((e) => this.handleAudioPlay(e))
        innerAudioContext.onPause((e) => this.handleAudioPause(e))
        innerAudioContext.onStop((e) => this.handleAudioPause(e))
        innerAudioContext.onEnded((e) => this.handleAudioPause(e))
        innerAudioContext.onError((e) => this.handleAudioError(e))
      } else {
        innerAudioContext.src = ''
      }
    },
  },

  /**
   * 组件的方法列表
   */
  methods: {
    /**
     * 播放音频
     */
    playAudio() {
      const { isPlaying } = this.data
      if (isPlaying) {
        innerAudioContext.pause()
      } else {
        innerAudioContext.play()
      }
    },

    /**
     * 音频播放成功
     */
    handleAudioPlay() {
      console.log('play: ', innerAudioContext)
      this.setData({
        audioPlay: true,
      })
    },

    /**
     * 音频播放暂停
     */
    handleAudioPause() {
      console.log('pause')
      this.setData({
        audioPlay: false,
      })
    },

    /**
     * 音频播放失败
     */
    handleAudioError(e) {
      console.log(e)
      wx.showToast({
        title: '播放失败',
        icon: 'none',
      })
    },
  },
})

本来是准备在 onCanplay 事件里获取音频时长的,但获取到的都是 0。所以还是用的录音时记录的时长。


小程序的摸索总是比 web 坎坷。