前一段時間的一個案例是開發一個有聲課件,大致就是通過導入文檔、圖片等資源後,頁面變為類似PPT 的佈局,然後選中一張圖片,可以插入音頻,有單頁編輯和全局編輯兩種模式。其中音訊的導入方式有兩種,一種是從資源庫中導入,還有一種就是要提到的錄音。
說實話,一開始都沒接觸過HTML5 的Audio API,而且要基於在我們接手前的程式碼中進行最佳化。當然其中也踩了不少坑,這次也會圍繞這幾個坑來說說感受(會省略一些基本對象的初始化和獲取,因為這些內容不是這次的重點,有興趣的同學可以自行查找MDN上的文檔):
開始錄音前,要先取得目前設備是否支援Audio API。早期的方法navigator.getUserMedia 已經被navigator.mediaDevices.getUserMedia 取代。正常來說現在大部分的現代瀏覽器都已經支援navigator.mediaDevices.getUserMedia 的用法了,當然MDN 上也給了相容性的寫法
const promisifiedOldGUM = function(constraints) { // First get ahold of getUserMedia, if present const getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia; with an error // to keep a consistent interface if (!getUserMedia) { return Promise.reject( new Error('getUserMedia is not implemented in this browser') ); } // Otherwise, wrap the call to the old this browser') ); } // Otherwise, wrap the call to the old nav browser') ); } // Otherwise, wrap the call to the old nav withigator.getMedia Promise return new Promise(function(resolve, reject) { getUserMedia.call(navigator, constraints, resolve, reject); });}; // Older browsers might not implement mediaDevices at all, so we set an empty object firstif (navigator.Devicalator.Devicores === definedator) {Devicigion.Devics === sdefvicator. {};} // Some browsers partially implement mediaDevices. We can't just assign an object// with getUserMedia as it would overwrite existing properties.// Here, we will just add the getUserMedia property if it's mis.if (navigator.UserDevicif. navigator.mediaDevices.getUserMedia = promisifiedOldGUM;}
因為這個方法是異步的,所以我們可以對無法相容的設備進行友好的提示
navigator.mediaDevices.getUserMedia(constraints).then( function(mediaStream) { // 成功}, function(error) { // 失敗const { name } = error; let errorMessage; switch (name) { // 使用者拒絕case ' NotAllowedError': case 'PermissionDeniedError': errorMessage = '使用者已禁止網頁呼叫錄音設備'; break; // 沒接入錄音設備case 'NotFoundError': case 'DevicesNotFoundError': errorMessage = '錄音設備未找到'; break; // 其它錯誤case 'NotSupportedError': errorMessage = '不支援錄音功能'; break; default: errorMessage = '錄音呼叫錯誤'; window.console.log(error); } return errorMessage; });
一切順利的話,我們就可以進入下一步了。
(這裡有對獲取上下文的方法進行了省略,因為這不是這次的重點)
開始錄音、暫停錄音這裡有個比較特別的點,就是需要加入一個中間變數來標識是否目前是否在錄音。因為在火狐瀏覽器上,我們發現一個問題,錄音的流程都是正常的,但是點擊暫停時卻發現怎麼也暫停不了,我們當時是使用disconnect 方法。這種方式是不行的,這種方法是需要斷開所有的連接才可以。後來發現,應該增加一個中間變數this.isRecording 來判斷目前是否正在錄音,當點擊開始時,將其設為true ,暫停時將其設為false 。
當我們開始錄音時,會有一個錄音監聽的事件onaudioprocess ,如果回傳true 則會將流寫入,如果傳回false 則不會將其寫入。因此判斷this.isRecording ,如果為false 則直接return
// 一些初始化const audioContext = new AudioContext();const sourceNode = audioContext.createMediaStreamSource(mediaStream);const scriptNode = audioContext.createScriptProcessor( BUFFER_SIZE, INPUT_CHANNELS_NUM, OUPUT_CHANNELS_NUM);sourceNode.connect(this.scriptNode);scriptNode.connect(this.audioContext.destination);// 監聽錄音的流程scriptNode.onaudioprocess = event => { if (!this.isRecording) return; // 是否判斷正規錄音this.buffers.push(event.inputBuffer.getChannelData(0)); //取得目前頻道的數據,並寫入數組};
當然這裡也會有個坑,就是無法再使用,自帶獲取當前錄音時長的方法了,因為實際上並不是真正的暫停,而是沒有將流寫入罷了。於是我們還需要取得目前錄音的時長,需要透過一個公式來取得
const getDuration = () => { return (4096 * this.buffers.length) / this.audioContext.sampleRate // 4096為一個流的長度,sampleRate 為取樣率}
這樣就能夠取得正確的錄音時長了。
結束錄音結束錄音的方式,我採用的是先暫停,之後需要試聽或者其它的操作先執行,然後再將存儲流的數組長度置為0。
獲取頻率getVoiceSize = analyser => { const dataArray = new Uint8Array(analyser.frequencyBinCount); analyser.getByteFrequencyData(dataArray); const data = dataArray.slice(100, 1000); const data = dataArray.slice(100, 1000); a + b); return sum;};
具體可以參考https://developer.mozilla.org/zh-CN/docs/Web/API/AnalyserNode/frequencyBinCount
其它
這次遇到的大部分問題都是相容性的問題,因此在上面踩了不少坑,尤其是移動端的問題,一開始還有出現因為獲取錄音時長寫法錯誤的問題,導致直接卡死的情況。這次的經驗也彌補了HTML5 API 上的一些空白,當然最重要的還是要提醒一下大家,這種原生的API 文件還是直接查看MDN 來的簡單粗暴!
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。