文章首發(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>
其中就兩個事件:handleRecordStart
和 handleRecordStop
萍聊。他們分別是長按時觸發(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 端確實是這樣狗热。但是小程序比較坑钞馁,如下圖:
音頻播放這里,我們選用了 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ū)提出疑問辕翰。