【微信小程序】小程序端錄音仲锄、播放指南

文章首發(fā):掘金主頁

一、前言

  • 開發(fā)背景:首次嘗試小程序中實現(xiàn)錄音掖桦、播放功能。
  • 開發(fā)框架:
    • taro 2.2.6
    • taro-ui 2.3.4
  • 難點描述:
    • 實現(xiàn)小程序錄音供汛、上傳到后臺
    • PC枪汪、IOS 和安卓端音頻播放資源的地址涌穆,支持 mp3 下載鏈接

溫馨提示:這篇文章重點介紹小程序的音頻在各種環(huán)境錄音和播放實踐。適用對象:遇到小程序在 IOS 端無法播放音頻的同學們和對小程序兼容性感興趣的同學雀久。

二宿稀、小程序錄音、上傳

2.1 注冊事件監(jiān)聽

首先赖捌,介紹一下錄音的部分原叮。這里主要用到了小程序中的 wx.getRecorderManager() 模塊部分。

直接放代碼巡蘸,感興趣的可以去微信開發(fā)文檔就了解下各種配置奋隶。

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'
      })
    })
    // 錄音結(jié)束時的處理
    recorderManager.onStop(res => {
      if (res.duration < 1000) {
        Taro.showToast({
          title: '錄音時間太短',
          duration: 1000,
          icon: 'none'
        })
      } else {
        // content 是存儲錄音結(jié)束后的數(shù)據(jù)結(jié)構(gòu),用于調(diào)試
        this.setState({ content: res })
        wx.saveFile({
          tempFilePath: res.tempFilePath,
          success: result => {
            // 這里會調(diào)用一個文件上傳的接口
            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 生命周期中唯欣,注冊幾個重要的事件。包括:監(jiān)聽錄音錯誤事件監(jiān)聽錄音結(jié)束事件
  • 在錄音結(jié)束時搬味,用 wx.savefile 將文件保存到本地
  • wx.savefile 成功的回調(diào)中境氢,調(diào)用文件上傳的接口,將文件上傳到服務(wù)器碰纬。

2.2 實現(xiàn)錄音事件處理函數(shù)

先看下 dom 節(jié)點部分:

<Text>上傳語音</Text>
<Text
  onLongPress={this.handleRecordStart}
  onTouchend={this.handleRecordStop}
>
  長按說話
</Text>

其中就兩個事件:handleRecordStarthandleRecordStop萍聊。他們分別是長按時觸發(fā)和手指松開時觸發(fā)。

簡單實現(xiàn):

// longpress (長按)時觸發(fā)
handleRecordStart(e) {
  this.setState({
    record: {
      // 修改錄音數(shù)據(jù)結(jié)構(gòu)悦析,此時錄音按鈕樣式會發(fā)生變化寿桨。
      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 (手指松開)時觸發(fā)
handleRecordStop() {
  // 復(fù)原在 start 方法中修改的錄音的數(shù)據(jù)結(jié)構(gòu)
  this.setState({
    record: {
      text: '長按錄音',
      type: 'record'
    }
  })
  // 結(jié)束錄音、隱藏 Toast 提示框
  wx.hideToast() 
  // 結(jié)束錄音
  this.recorderManager.stop() 
}

這里用了一個 record 對象來記錄錄音的狀態(tài)强戴。

注意 recorderManager.start 方法的參數(shù)中亭螟, duration 指錄音時長,這里設(shè)置為 60000 ms骑歹;format 值為 mp3预烙,意思錄音得到的音頻文件為 mp3 格式。

溫馨提示:最初開發(fā)沒有設(shè)置成格式化為 mp3道媚,導(dǎo)致后臺同事增加了工作量(將 m4a 轉(zhuǎn)換成 mp3)扁掸,這里建議前端直接處理,很方便。

三、小程序端錄音的播放

3.1 錄音播放

說到音頻播放廉沮,大家第一時間可能想到的是 Audio 標簽,然后給其中的 src 屬性動態(tài)賦值就好了狸剃。沒錯掐隐,PC 端確實是這樣狗热。但是小程序比較坑钞馁,如下圖:

image

音頻播放這里,我們選用了 wx.createInnerAudioContext() 接口匿刮。

溫馨提示:如果音頻上傳到后臺之后可以返回 .mp3 結(jié)尾的 url 鏈接(例如:http://47.104.167.164/faceVideo/result_2020_07_21_12_33_43.mp3)僧凰,可以考慮直接利用 wx.createInnerAudioContext()play() 方法實現(xiàn)播放。

由于部分原因熟丸,我們后臺上傳音頻文件后训措,返回的鏈接是一個云文件 ID(指瀏覽器打開可以下載此 mp3 文件)。而且經(jīng)過測試發(fā)現(xiàn)光羞,安卓端可以直接播放绩鸣,IOS 端直接播放沒有聲音。

然后纱兑,請教了一下我們組的架構(gòu)師呀闻,決定將文件先下載下來,然后保存到手機本地潜慎,最后播放(經(jīng)過測試方案可行)捡多。

我們直接看代碼:

// 小程序音頻播放 api
innerAudioContext = wx.createInnerAudioContext()

// 下載音頻文件
downloadFile() {
  const FileSystemManager = wx.getFileSystemManager()
  const { voiceUrl } = this.state
  wx.downloadFile({
    url: voiceUrl,
    header: { 'Content-type': 'audio/mp3' },
    success: res => {
      // 只要服務(wù)器有響應(yīng)數(shù)據(jù),就會把響應(yīng)內(nèi)容寫入文件并進入 success 回調(diào)铐炫,業(yè)務(wù)需要自行判斷是否下載到了想要的內(nèi)容
      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 端靜音狀態(tài)沒法播放的問題
  this.innerAudioContext.obeyMuteSwitch = false
  this.innerAudioContext.onEnded(res => {
    // isPlaying 記錄是否在播放中
    this.setState({ isPlaying: false })
    this.innerAudioContext.stop()
  })
  this.innerAudioContext.onError(res => {
    // 播放音頻失敗的回調(diào)
  })
  this.innerAudioContext.onPlay(res => {
    // 開始播放音頻的回調(diào)
  })
  this.innerAudioContext.onStop(res => {
    // 播放音頻停止的回調(diào)
  })
}

這里做了兩件事情:

  • wx.downloadFile() 接口將文件下載下來垒手,注意參數(shù)中 header 屬性, Content-type 值為 audio/mp3倒信。即將此文件識別為音頻類文件科贬。這里用到微信里的文件管理器 wx.getFileSystemManager() ,接口中的 saveFile() 方法可以把文件保存到本地
  • wx.createInnerAudioContext()play() 方法播放存在本地的音樂 mp3 文件

3.2 性能優(yōu)化

這里考慮到播放完之后鳖悠,存在手機的錄音文件會越來越多唆迁。我們想想辦法,做一做性能優(yōu)化工作竞穷。也就是在恰當?shù)臅r機清楚多余文件唐责。

代碼如下:

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)的所有文件。在其讀取成功的回調(diào)中做一個 forEach 循環(huán)瘾带,然后用 unlink() 刪除文件鼠哥。最后將此方法放在生命周期 componentWillUnmount 中調(diào)用。

四看政、PC 端音頻播放

小程序的錄音和播放都簡單的介紹了朴恳,這里也拓展一下。說一說 PC 端比較原始的音頻播放方法允蚣。

項目中沒有引用播放器插件于颖,這里直接用 audio 標簽來實現(xiàn)。 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;
  // 區(qū)分播放還是暫停
  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);
  });
};

// 在瀏覽器上預(yù)覽音頻文件
executeDownload = (data: any) => {
  if (!data) {
    return;
  }
  // 將文件轉(zhuǎn)化音頻流的鏈接
  const url = window.URL.createObjectURL(new Blob([data], { type: 'audio/mp3' }));
  // 前端存儲這個鏈接
  this.setState({ voice_url: url });
};

梳理:

  • 創(chuàng)建 audio 標簽作為音頻播放的容器
  • 點擊頁面的播放按鈕觸發(fā)文件下載方法
  • 通過 axios 下載資源文件同衣,用 new FileReader() 讀取文件竟块,并且在文件完全加載時,利用 window.URL.createObjectURL() 方法生成可以在瀏覽器上預(yù)覽音頻文件的鏈接
  • audio 監(jiān)聽到 src 屬性的變化時耐齐,會自動播放出聲音

五浪秘、感謝

  • 如果本文對你有幫助,就點個贊支持下吧埠况!感謝閱讀耸携。
  • 文中還有很多不完善的地方,歡迎大家在評論區(qū)提出疑問辕翰。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末违帆,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子金蜀,更是在濱河造成了極大的恐慌刷后,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件渊抄,死亡現(xiàn)場離奇詭異尝胆,居然都是意外死亡,警方通過查閱死者的電腦和手機护桦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進店門含衔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人二庵,你說我怎么就攤上這事贪染。” “怎么了催享?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵杭隙,是天一觀的道長。 經(jīng)常有香客問我因妙,道長痰憎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任攀涵,我火速辦了婚禮铣耘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘以故。我一直安慰自己蜗细,他們只是感情好,可當我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布怒详。 她就那樣靜靜地躺著炉媒,像睡著了一般踪区。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上橱野,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天,我揣著相機與錄音善玫,去河邊找鬼水援。 笑死,一個胖子當著我的面吹牛茅郎,可吹牛的內(nèi)容都是我干的蜗元。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼系冗,長吁一口氣:“原來是場噩夢啊……” “哼奕扣!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起掌敬,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤惯豆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后奔害,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體楷兽,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年华临,在試婚紗的時候發(fā)現(xiàn)自己被綠了芯杀。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡雅潭,死狀恐怖揭厚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情扶供,我是刑警寧澤筛圆,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站椿浓,受9級特大地震影響顽染,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜轰绵,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一粉寞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧左腔,春花似錦唧垦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽巧还。三九已至,卻和暖如春坊秸,著一層夾襖步出監(jiān)牢的瞬間麸祷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工褒搔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留阶牍,地道東北人。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓星瘾,卻偏偏與公主長得像走孽,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子琳状,可洞房花燭夜當晚...
    茶點故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內(nèi)容