美文网首页程序员全栈之巅
【微信小程序】小程序端录音、播放指南

【微信小程序】小程序端录音、播放指南

作者: 旭哥_ | 来源:发表于2020-12-07 09:36 被阅读0次

    文章首发:掘金主页

    一、前言

    • 开发背景:首次尝试小程序中实现录音、播放功能。
    • 开发框架:
      • taro 2.2.6
      • taro-ui 2.3.4
    • 难点描述:
      • 实现小程序录音、上传到后台
      • PC、IOS 和安卓端音频播放资源的地址,支持 mp3 下载链接

    温馨提示:这篇文章重点介绍小程序的音频在各种环境录音和播放实践。适用对象:遇到小程序在 IOS 端无法播放音频的同学们和对小程序兼容性感兴趣的同学。

    二、小程序录音、上传

    2.1 注册事件监听

    首先,介绍一下录音的部分。这里主要用到了小程序中的 wx.getRecorderManager() 模块部分。

    直接放代码,感兴趣的可以去微信开发文档就了解下各种配置。

    import Taro, { Component } from '@tarojs/taro'
    
    export default class Index extends Component {
      ...
      // 声明录音管理器模块
      recorderManager = wx.getRecorderManager()
    
      componentDidMount() {
        // 抛出错误
        recorderManager.onError(() => {
          Taro.showToast({
            title: '录音失败!',
            duration: 1000,
            icon: 'none'
          })
        })
        // 录音结束时的处理
        recorderManager.onStop(res => {
          if (res.duration < 1000) {
            Taro.showToast({
              title: '录音时间太短',
              duration: 1000,
              icon: 'none'
            })
          } else {
            // content 是存储录音结束后的数据结构,用于调试
            this.setState({ content: res })
            wx.saveFile({
              tempFilePath: res.tempFilePath,
              success: result => {
                // 这里会调用一个文件上传的接口
                this.fileUpload(result.savedFilePath)
              }
            })
          }
        })
      }
      
      fileUpload(tempFilePath) {
        Taro.uploadFile({
          url: XXXApi,
          filePath: tempFilePath,
          name: 'file',
          header: {
            'content-type': 'multipart/form-data',
            cookie: Taro.getStorageSync('cookie') // 上传需要单独处理 cookie
          },
          formData: {
            method: 'POST' // 请求方式
          },
          success: res => {
            // 录音上传成功之后的处理
          }
        })
      }
    }
    

    梳理一下:

    • componentDidMount 生命周期中,注册几个重要的事件。包括:监听录音错误事件监听录音结束事件
    • 在录音结束时,用 wx.savefile 将文件保存到本地
    • wx.savefile 成功的回调中,调用文件上传的接口,将文件上传到服务器。

    2.2 实现录音事件处理函数

    先看下 dom 节点部分:

    <Text>上传语音</Text>
    <Text
      onLongPress={this.handleRecordStart}
      onTouchend={this.handleRecordStop}
    >
      长按说话
    </Text>
    

    其中就两个事件:handleRecordStarthandleRecordStop。他们分别是长按时触发和手指松开时触发。

    简单实现:

    // longpress (长按)时触发
    handleRecordStart(e) {
      this.setState({
        record: {
          // 修改录音数据结构,此时录音按钮样式会发生变化。
          text: '松开保存',
          type: 'recording'
        }
      })
      // 开始录音
      this.recorderManager.start({
        duration: 60000,
        sampleRate: 44100,
        numberOfChannels: 1,
        encodeBitRate: 192000,
        format: 'mp3',
        frameSize: 50
      }) 
      Taro.showToast({
        title: '正在录音',
        duration: 60000,
        icon: 'none'
      })
    }
    
    // touchend (手指松开)时触发
    handleRecordStop() {
      // 复原在 start 方法中修改的录音的数据结构
      this.setState({
        record: {
          text: '长按录音',
          type: 'record'
        }
      })
      // 结束录音、隐藏 Toast 提示框
      wx.hideToast() 
      // 结束录音
      this.recorderManager.stop() 
    }
    

    这里用了一个 record 对象来记录录音的状态。

    注意 recorderManager.start 方法的参数中, duration 指录音时长,这里设置为 60000 msformat 值为 mp3,意思录音得到的音频文件为 mp3 格式。

    温馨提示:最初开发没有设置成格式化为 mp3,导致后台同事增加了工作量(将 m4a 转换成 mp3),这里建议前端直接处理,很方便。

    三、小程序端录音的播放

    3.1 录音播放

    说到音频播放,大家第一时间可能想到的是 Audio 标签,然后给其中的 src 属性动态赋值就好了。没错,PC 端确实是这样。但是小程序比较坑,如下图:

    image

    音频播放这里,我们选用了 wx.createInnerAudioContext() 接口。

    温馨提示:如果音频上传到后台之后可以返回 .mp3 结尾的 url 链接(例如:http://47.104.167.164/faceVideo/result_2020_07_21_12_33_43.mp3),可以考虑直接利用 wx.createInnerAudioContext()play() 方法实现播放。

    由于部分原因,我们后台上传音频文件后,返回的链接是一个云文件 ID(指浏览器打开可以下载此 mp3 文件)。而且经过测试发现,安卓端可以直接播放,IOS 端直接播放没有声音。

    然后,请教了一下我们组的架构师,决定将文件先下载下来,然后保存到手机本地,最后播放(经过测试方案可行)。

    我们直接看代码:

    // 小程序音频播放 api
    innerAudioContext = wx.createInnerAudioContext()
    
    // 下载音频文件
    downloadFile() {
      const FileSystemManager = wx.getFileSystemManager()
      const { voiceUrl } = this.state
      wx.downloadFile({
        url: voiceUrl,
        header: { 'Content-type': 'audio/mp3' },
        success: res => {
          // 只要服务器有响应数据,就会把响应内容写入文件并进入 success 回调,业务需要自行判断是否下载到了想要的内容
          if (res.statusCode === 200) {
            FileSystemManager.saveFile({
              tempFilePath: res.tempFilePath,
              // 文件地址为手机本地
              filePath: `${wx.env.USER_DATA_PATH}/${new Date().getTime()}.mp3`,
              success: result => {
                if (result.errMsg == 'saveFile:ok') {
                  this.registerAudioContext(result.savedFilePath)
                }
              }
            })
          }
        }
      })
    }
    
    // 注册音频控件
    registerAudioContext(path) {
      this.innerAudioContext.src = path
      this.innerAudioContext.play()
      // 避开 IOS 端静音状态没法播放的问题
      this.innerAudioContext.obeyMuteSwitch = false
      this.innerAudioContext.onEnded(res => {
        // isPlaying 记录是否在播放中
        this.setState({ isPlaying: false })
        this.innerAudioContext.stop()
      })
      this.innerAudioContext.onError(res => {
        // 播放音频失败的回调
      })
      this.innerAudioContext.onPlay(res => {
        // 开始播放音频的回调
      })
      this.innerAudioContext.onStop(res => {
        // 播放音频停止的回调
      })
    }
    

    这里做了两件事情:

    • wx.downloadFile() 接口将文件下载下来,注意参数中 header 属性, Content-type 值为 audio/mp3。即将此文件识别为音频类文件。这里用到微信里的文件管理器 wx.getFileSystemManager() ,接口中的 saveFile() 方法可以把文件保存到本地
    • wx.createInnerAudioContext()play() 方法播放存在本地的音乐 mp3 文件

    3.2 性能优化

    这里考虑到播放完之后,存在手机的录音文件会越来越多。我们想想办法,做一做性能优化工作。也就是在恰当的时机清楚多余文件。

    代码如下:

    componentWillUnmount() {
      this.clearDir()
    }
    
    // 删除下载的音频文件
    clearDir() {
      const FileSystemManager = wx.getFileSystemManager()
      const __dirPath = wx.env.USER_DATA_PATH
      FileSystemManager.readdir({
        dirPath: __dirPath,
        success: res => {
          const { errMsg, files } = res
          if (errMsg == 'readdir:ok') {
            files.forEach(item => {
              FileSystemManager.unlink({
                filePath: `${__dirPath}/${item}`
              })
            })
          }
        }
      })
    }
    

    梳理一下:

    wx.getFileSystemManager() 接口中 readdir() 方法读取到指定目录(wx.env.USER_DATA_PATH)的所有文件。在其读取成功的回调中做一个 forEach 循环,然后用 unlink() 删除文件。最后将此方法放在生命周期 componentWillUnmount 中调用。

    四、PC 端音频播放

    小程序的录音和播放都简单的介绍了,这里也拓展一下。说一说 PC 端比较原始的音频播放方法。

    项目中没有引用播放器插件,这里直接用 audio 标签来实现。 html 的部分如下:

    const { voice_url, isPlaying } = this.state;
    
    return (
      <>
        <p>
          <span>音频:</span>
          <Button onClick={this.onBtnClick}>{isPlaying ? '停止' : '播放'}</Button>
        </p>
    
        <audio
          id={`audio`}
          src={voice_url}
          autoPlay={true}
          ref={this.audioRef}
          preload={'auto'}
          onCanPlay={() => {}}
          onTimeUpdate={() => {}}>
          <track src={voice_url} kind='captions' />
        </audio>
      </>
    )
    

    然后看下 PC 端解析播放部分,和小程序原理差不多,先下载,后播放。代码如下:

    // 播放或者暂停
    onBtnClick = () => {
      const { isPlaying } = this.state;
      // 区分播放还是暂停
      if (isPlaying) {
        this.audioRef.current.pause();
      } else {
        this.downloadFile();
      }
      this.setState({ isPlaying: !isPlaying });
    };
    
    // 下载文件
    downloadFile = () => {
      const { download_url } = this.state;
      axios.get(download_url as string, { responseType: 'blob' }).then((res: any) => {
        const reader = new FileReader();
        const data = res.data;
        reader.onload = e => {
          this.executeDownload(data);
        };
        reader.readAsText(data);
      });
    };
    
    // 在浏览器上预览音频文件
    executeDownload = (data: any) => {
      if (!data) {
        return;
      }
      // 将文件转化音频流的链接
      const url = window.URL.createObjectURL(new Blob([data], { type: 'audio/mp3' }));
      // 前端存储这个链接
      this.setState({ voice_url: url });
    };
    

    梳理:

    • 创建 audio 标签作为音频播放的容器
    • 点击页面的播放按钮触发文件下载方法
    • 通过 axios 下载资源文件,用 new FileReader() 读取文件,并且在文件完全加载时,利用 window.URL.createObjectURL() 方法生成可以在浏览器上预览音频文件的链接
    • audio 监听到 src 属性的变化时,会自动播放出声音

    五、感谢

    • 如果本文对你有帮助,就点个赞支持下吧!感谢阅读。
    • 文中还有很多不完善的地方,欢迎大家在评论区提出疑问。

    相关文章

      网友评论

        本文标题:【微信小程序】小程序端录音、播放指南

        本文链接:https://www.haomeiwen.com/subject/aaszwktx.html