知識
不管是網(wǎng)站,軟件還是小程序,都要直接或間接能為您產(chǎn)生價(jià)值,我們在追求其視覺表現(xiàn)的同時(shí),更側(cè)重于功能的便捷,營銷的便利,運(yùn)營的高效,讓網(wǎng)站成為營銷工具,讓軟件能切實(shí)提升企業(yè)內(nèi)部管理水平和效率。優(yōu)秀的程序?yàn)楹笃谏壧峁┍憬莸闹С郑?
您當(dāng)前位置>首頁 » 新聞資訊 » 小程序相關(guān) >
【微信小程序】小程序端錄音、播放踩坑日記
發(fā)表時(shí)間:2021-1-5
發(fā)布人:葵宇科技
瀏覽次數(shù):171
- 開發(fā)背景:首次嘗試小程序中實(shí)現(xiàn)錄音、播放功能。
- 開發(fā)框架:
- taro 2.2.6
- taro-ui 2.3.4
- 難點(diǎn)描述:
- 實(shí)現(xiàn)小程序錄音、上傳到后臺
- PC、IOS 和安卓端音頻播放資源的地址,支持 mp3 下載鏈接
二、小程序錄音、上傳溫馨提示:這篇文章重點(diǎn)介紹小程序的音頻在各種環(huán)境錄音和播放實(shí)踐。適用對象:遇到小程序在 IOS 端無法播放音頻的同學(xué)們和對小程序兼容性感興趣的同學(xué)。
2.1 注冊事件監(jiān)聽
首先,介紹一下錄音的部分。這里主要用到了小程序中的 wx.getRecorderManager()
模塊部分。
直接放代碼,感興趣的可以去微信開發(fā)文檔就了解下各種配置。
import Taro, { Component } from '@tarojs/taro'
export default class Index extends Component {
...
// 聲明錄音管理器模塊
recorderManager = wx.getRecorderManager()
componentDidMount() {
// 拋出錯(cuò)誤
recorderManager.onError(() => {
Taro.showToast({
title: '錄音失??!',
duration: 1000,
icon: 'none'
})
})
// 錄音結(jié)束時(shí)的處理
recorderManager.onStop(res => {
if (res.duration < 1000) {
Taro.showToast({
title: '錄音時(shí)間太短',
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)用一個(gè)文件上傳的接口
this.fileUpload(result.savedFilePath)
}
})
}
})
}
fileUpload(tempFilePath) {
Taro.uploadFile({
url: XXXApi,
filePath: tempFilePath,
name: 'file',
header: {
'content-type': 'multipart/form-data',
cookie: Taro.getStorageSync('cookie') // 上傳需要單獨(dú)處理 cookie
},
formData: {
method: 'POST' // 請求方式
},
success: res => {
// 錄音上傳成功之后的處理
}
})
}
}
復(fù)制代碼
梳理一下:
- 在
componentDidMount
生命周期中,注冊幾個(gè)重要的事件。包括:監(jiān)聽錄音錯(cuò)誤事件和監(jiān)聽錄音結(jié)束事件 - 在錄音結(jié)束時(shí),用
wx.savefile
將文件保存到本地 - 在
wx.savefile
成功的回調(diào)中,調(diào)用文件上傳的接口,將文件上傳到服務(wù)器。
先看下 dom
節(jié)點(diǎn)部分:
<Text>上傳語音</Text>
<Text
onLongPress={this.handleRecordStart}
onTouchend={this.handleRecordStop}
>
長按說話
</Text>
復(fù)制代碼
其中就兩個(gè)事件:handleRecordStart
和 handleRecordStop
。他們分別是長按時(shí)觸發(fā)和手指松開時(shí)觸發(fā)。
簡單實(shí)現(xiàn):
// longpress (長按)時(shí)觸發(fā)
handleRecordStart(e) {
this.setState({
record: {
// 修改錄音數(shù)據(jù)結(jié)構(gòu),此時(shí)錄音按鈕樣式會發(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 (手指松開)時(shí)觸發(fā)
handleRecordStop() {
// 復(fù)原在 start 方法中修改的錄音的數(shù)據(jù)結(jié)構(gòu)
this.setState({
record: {
text: '長按錄音',
type: 'record'
}
})
// 結(jié)束錄音、隱藏 Toast 提示框
wx.hideToast()
// 結(jié)束錄音
this.recorderManager.stop()
}
復(fù)制代碼
這里用了一個(gè) record
對象來記錄錄音的狀態(tài)。
注意 recorderManager.start
方法的參數(shù)中, duration
指錄音時(shí)長,這里設(shè)置為 60000 ms
;format
值為 mp3
,意思錄音得到的音頻文件為 mp3
格式。
溫馨提示:最初開發(fā)沒有設(shè)置成格式化為
mp3
,導(dǎo)致后臺同事增加了工作量(將m4a
轉(zhuǎn)換成mp3
),這里建議前端直接處理,很方便。
3.1 錄音播放
說到音頻播放,大家第一時(shí)間可能想到的是 Audio
標(biāo)簽,然后給其中的 src
屬性動態(tài)賦值就好了。沒錯(cuò),PC 端確實(shí)是這樣。但是小程序比較坑,如下圖:
音頻播放這里,我們選用了 wx.createInnerAudioContext()
接口。
溫馨提示:如果音頻上傳到后臺之后可以返回
.mp3
結(jié)尾的url
鏈接(例如:http://47.104.167.164/faceVideo/result_2020_07_21_12_33_43.mp3
),可以考慮直接利用wx.createInnerAudioContext()
的play()
方法實(shí)現(xiàn)播放。
由于部分原因,我們后臺上傳音頻文件后,返回的鏈接是一個(gè)云文件 ID(指瀏覽器打開可以下載此 mp3 文件)。而且經(jīng)過測試發(fā)現(xiàn),安卓端可以直接播放,IOS 端直接播放沒有聲音。
然后,請教了一下我們組的架構(gòu)師,決定將文件先下載下來,然后保存到手機(jī)本地,最后播放(經(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)容寫入文件并進(jìn)入 success 回調(diào),業(yè)務(wù)需要自行判斷是否下載到了想要的內(nèi)容
if (res.statusCode === 200) {
FileSystemManager.saveFile({
tempFilePath: res.tempFilePath,
// 文件地址為手機(jī)本地
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 = http://www.wxapp-union.com/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)
})
}
復(fù)制代碼
這里做了兩件事情:
- 用
wx.downloadFile()
接口將文件下載下來,注意參數(shù)中header
屬性,Content-type
值為audio/mp3
。即將此文件識別為音頻類文件。這里用到微信里的文件管理器wx.getFileSystemManager()
,接口中的saveFile()
方法可以把文件保存到本地 - 用
wx.createInnerAudioContext()
的play()
方法播放存在本地的音樂mp3
文件
這里考慮到播放完之后,存在手機(jī)的錄音文件會越來越多。我們想想辦法,做一做性能優(yōu)化工作。也就是在恰當(dāng)?shù)臅r(shí)機(jī)清楚多余文件。
代碼如下:
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}`
})
})
}
}
})
}
復(fù)制代碼
梳理一下:
用 wx.getFileSystemManager()
接口中 readdir()
方法讀取到指定目錄(wx.env.USER_DATA_PATH
)的所有文件。在其讀取成功的回調(diào)中做一個(gè) forEach
循環(huán),然后用 unlink()
刪除文件。最后將此方法放在生命周期 componentWillUnmount
中調(diào)用。
小程序的錄音和播放都簡單的介紹了,這里也拓展一下。說一說 PC 端比較原始的音頻播放方法。
項(xiàng)目中沒有引用播放器插件,這里直接用 audio
標(biāo)簽來實(shí)現(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>
</>
)
復(fù)制代碼
然后看下 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 = http://www.wxapp-union.com/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' }));
// 前端存儲這個(gè)鏈接
this.setState({ voice_url: url });
};
復(fù)制代碼
梳理:
- 創(chuàng)建
audio
標(biāo)簽作為音頻播放的容器 - 點(diǎn)擊頁面的播放按鈕觸發(fā)文件下載方法
- 通過
axios
下載資源文件,用new FileReader()
讀取文件,并且在文件完全加載時(shí),利用window.URL.createObjectURL()
方法生成可以在瀏覽器上預(yù)覽音頻文件的鏈接 audio
監(jiān)聽到src
屬性的變化時(shí),會自動播放出聲音
相關(guān)案例查看更多
相關(guān)閱讀
- Web開發(fā)框架
- 昆明網(wǎng)站制作
- 軟件定制公司
- .net網(wǎng)站
- 昆明軟件公司
- 云南小程序商城
- 小程序定制
- 紅河小程序開發(fā)
- 云南做網(wǎng)站
- 云南軟件公司
- 云南網(wǎng)站建設(shè)列表網(wǎng)
- web開發(fā)技術(shù)
- 楚雄小程序開發(fā)
- 小程序制作
- 昆明小程序公司
- 云南小程序開發(fā)制作
- 云南網(wǎng)頁制作
- 云南etc小程序
- 云南網(wǎng)站建設(shè)公司地址
- 網(wǎng)站建設(shè)案例
- 云南小程序開發(fā)公司推薦
- 云南網(wǎng)站建設(shè)首頁
- 報(bào)廢車回收管理軟件
- 正規(guī)網(wǎng)站建設(shè)公司
- 網(wǎng)站沒排名
- 小程序被攻擊
- 云南小程序制作
- 云南網(wǎng)站建設(shè)高手
- 網(wǎng)站制作
- 網(wǎng)站建設(shè)服務(wù)公司