JavaScript+HTML?實(shí)現(xiàn)網(wǎng)頁(yè)錄制音頻與下載
HTML + JavaScript 實(shí)現(xiàn)網(wǎng)頁(yè)錄制音頻與下載
簡(jiǎn)介
在這個(gè)數(shù)字化的時(shí)代,網(wǎng)頁(yè)端的音頻處理能力已經(jīng)成為一個(gè)非常熱門(mén)的需求。本文將詳細(xì)介紹如何利用 getUserMedia 和 MediaRecorder 這兩個(gè)強(qiáng)大的 API,實(shí)現(xiàn)網(wǎng)頁(yè)端音頻的錄制、處理和播放等功能。
getUserMedia
getUserMedia 和 MediaRecorder 是 HTML5 中兩個(gè)非常重要的 API,用于訪問(wèn)設(shè)備媒體輸入流并對(duì)其進(jìn)行操作。
getUserMedia 允許網(wǎng)頁(yè)端訪問(wèn)用戶(hù)設(shè)備的媒體輸入設(shè)備,比如攝像頭和麥克風(fēng)。通過(guò)該 API,在獲得用戶(hù)授權(quán)后,我們可以獲取這些媒體流的數(shù)據(jù),并用于各種網(wǎng)頁(yè)應(yīng)用場(chǎng)景中。
典型的使用方式如下:
// 請(qǐng)求獲取音頻流 navigator.mediaDevices.getUserMedia({ audio: true }) .then(stream => { // 在此處理音頻流 })
getUserMedia 接受一個(gè) constraints 對(duì)象作為參數(shù),通過(guò)設(shè)置配置來(lái)請(qǐng)求獲取指定的媒體類(lèi)型,常見(jiàn)的配置有:
- audio:Boolean 值,是否獲取音頻輸入。
- video:Boolean 值,是否獲取視頻輸入。
- 以及更詳細(xì)的各種音視頻參數(shù)設(shè)置。
MediaRecorder
MediaRecorder API 可以獲取由 getUserMedia 生成的媒體流,并對(duì)其進(jìn)行編碼和封裝,輸出可供播放和傳輸?shù)拿襟w文件。
典型的用法如下:
// 獲取媒體流 const stream = await navigator.mediaDevices.getUserMedia({ audio: true }) // 創(chuàng)建 MediaRecorder 實(shí)例 const mediaRecorder = new MediaRecorder(stream); // 注冊(cè)數(shù)據(jù)可用事件,以獲取編碼后的媒體數(shù)據(jù)塊 mediaRecorder.ondataavailable = event => { audioChunks.push(event.data); } // 開(kāi)始錄制 mediaRecorder.start(); // 錄制完成后停止 mediaRecorder.stop(); // 將錄制的數(shù)據(jù)組裝成 Blob const blob = new Blob(audioChunks, { type: 'audio/mp3' });
簡(jiǎn)單來(lái)說(shuō),getUserMedia 獲取輸入流,MediaRecorder 對(duì)流進(jìn)行編碼和處理,兩者結(jié)合就可以實(shí)現(xiàn)強(qiáng)大的音視頻處理能力。
獲取和處理音頻流
了解了基本 API 使用方法后,我們來(lái)看看如何獲取和處理音頻流。
首先需要調(diào)用 getUserMedia 來(lái)獲取音頻流,典型的配置是:
const stream = await navigator.mediaDevices.getUserMedia({ audio: { channelCount: 2, sampleRate: 44100, sampleSize: 16, echoCancellation: true } });
我們可以指定聲道數(shù)、采樣率、采樣大小等參數(shù)來(lái)獲取音頻流。
PS:這似乎不管用。
使用 navigator.mediaDevices.enumerateDevices() 可以獲得所有可用的媒體設(shè)備列表,這樣我們就可以提供設(shè)備選擇功能給用戶(hù),而不僅僅是默認(rèn)設(shè)備。
舉例來(lái)說(shuō),如果我們想要讓用戶(hù)選擇要使用的錄音設(shè)備:
// 1. 獲取錄音設(shè)備列表 const audioDevices = await navigator.mediaDevices.enumerateDevices(); const mics = audioDevices.filter(d => d.kind === 'audioinput'); // 2. 提供設(shè)備選擇 UI 供用戶(hù)選擇 const selectedMic = mics[0]; // 3. 根據(jù)選擇配置進(jìn)行獲取流 const constraints = { audio: { deviceId: selectedMic.deviceId } }; const stream = await navigator.mediaDevices.getUserMedia(constraints);
這樣我們就可以獲得用戶(hù)選擇的設(shè)備錄音了。
獲得原始音頻流后,我們可以利用 Web Audio API 對(duì)其進(jìn)行處理。
例如添加回聲效果:
// 創(chuàng)建音頻環(huán)境 const audioContext = new AudioContext(); // 創(chuàng)建流源節(jié)點(diǎn) const source = audioContext.createMediaStreamSource(stream); // 創(chuàng)建回聲效果節(jié)點(diǎn) const echo = audioContext.createConvolver(); // 連接處理鏈 source.connect(echo); echo.connect(audioContext.destination); // 加載回聲沖擊響應(yīng)并應(yīng)用 const impulseResponse = await fetch('impulse.wav'); const buffer = await impulseResponse.arrayBuffer(); const audioBuffer = await audioContext.decodeAudioData(buffer); echo.buffer = audioBuffer;
通過(guò)這樣的音頻處理鏈,我們就可以在錄音時(shí)添加回聲、混響等音效了。
實(shí)現(xiàn)音頻的錄制和播放
錄制音頻的步驟:
- 調(diào)用 getUserMedia 獲取音頻流。
- 創(chuàng)建 MediaRecorder 實(shí)例,傳入音頻流。
- 注冊(cè)數(shù)據(jù)可用回調(diào),以獲取編碼后的音頻數(shù)據(jù)塊。
- 調(diào)用 recorder.start() 開(kāi)始錄制。
- 錄制完成后調(diào)用 recorder.stop()。
代碼:
let recorder; let audioChunks = []; // 開(kāi)始錄音 handler const startRecording = async () => { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); recorder = new MediaRecorder(stream); recorder.ondataavailable = event => { audioChunks.push(event.data); }; recorder.start(); } // 停止錄音 handler const stopRecording = () => { if(recorder.state === "recording") { recorder.stop(); } }
錄音完成后,我們可以將音頻數(shù)據(jù)組裝成一個(gè) Blob 對(duì)象,然后賦值給一個(gè) <audio> 元素的 src 屬性進(jìn)行播放。
代碼:
// 錄音停止后 const blob = new Blob(audioChunks, { type: 'audio/ogg' }); const audioURL = URL.createObjectURL(blob); const player = document.querySelector('audio'); player.src = audioURL; // 調(diào)用播放 player.play();
這樣就可以播放剛剛錄制的音頻了。
后續(xù)也可以添加下載功能等。
音頻效果的處理
利用 Web Audio API,我們可以添加各種音頻效果,進(jìn)行音頻處理。
例如添加回聲效果:
const audioContext = new AudioContext(); // 原始音頻節(jié)點(diǎn) const source = audioContext.createMediaStreamSource(stream); // 回聲效果節(jié)點(diǎn) const echo = audioContext.createConvolver(); // 連接處理鏈 source.connect(echo); echo.connect(audioContext.destination); // 加載沖擊響應(yīng)作為回聲效果 const impulseResponse = await fetch('impulse.wav'); const arrayBuffer = await impulseResponse.arrayBuffer(); const audioBuffer = await audioContext.decodeAudioData(arrayBuffer); echo.buffer = audioBuffer;
這樣在錄制時(shí)音頻流就會(huì)經(jīng)過(guò)回聲效果處理了。
此外,我們還可以添加混響、濾波、均衡器、壓縮等多種音頻效果,使得網(wǎng)頁(yè)端也能處理出專(zhuān)業(yè)級(jí)的音頻作品。
實(shí)時(shí)語(yǔ)音通話(huà)的應(yīng)用
利用 getUserMedia 和 WebRTC 技術(shù),我們還可以在網(wǎng)頁(yè)端實(shí)現(xiàn)實(shí)時(shí)的點(diǎn)對(duì)點(diǎn)語(yǔ)音通話(huà)。
簡(jiǎn)述流程如下:
- 通過(guò) getUserMedia 獲取本地音視頻流。
- 創(chuàng)建 RTCPeerConnection 實(shí)例。
- 將本地流添加到連接上。
- 交換 ICE 候選信息,建立連接。
- 當(dāng)檢測(cè)到連接后,渲染遠(yuǎn)端用戶(hù)的音視頻流。
這樣就可以實(shí)現(xiàn)類(lèi)似 Skype 的網(wǎng)頁(yè)端語(yǔ)音通話(huà)功能了。
代碼:
// 1. 獲取本地流 const localStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true }); // 2. 創(chuàng)建連接對(duì)象 const pc = new RTCPeerConnection(); // 3. 添加本地流 localStream.getTracks().forEach(track => pc.addTrack(track, localStream)); // 4. 交換 ICE 等信令,處理 ONADDSTREAM 等事件 // ... // 5. 收到遠(yuǎn)端流,渲染到頁(yè)面 pc.ontrack = event => { remoteVideo.srcObject = event.streams[0]; }
獲取本地輸入流后,經(jīng)過(guò)編碼和傳輸就可以實(shí)現(xiàn)語(yǔ)音聊天了。
兼容性和 Latency 問(wèn)題
盡管 getUserMedia 和 MediaRecorder 在現(xiàn)代瀏覽器中已經(jīng)得到了較好的支持,但由于不同廠商和版本實(shí)現(xiàn)存在差異,在實(shí)際應(yīng)用中還是需要注意一些兼容性問(wèn)題:
- 檢測(cè) API 支持情況,提供降級(jí)方案。
- 注意不同瀏覽器對(duì) Codec、采樣率等參數(shù)支持的差異。
- 封裝瀏覽器差異,提供統(tǒng)一的 API。
此外,錄音和播放也存在一定的延遲問(wèn)題。我們需要針對(duì) Latency 進(jìn)行優(yōu)化,比如使用更小的 buffer 大小,壓縮數(shù)據(jù)包大小等方法。
項(xiàng)目代碼
record.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Record Page</title> <link rel="stylesheet" type="text/css" href="css/record.css" rel="external nofollow" > </head> <body> <div class="app"> <audio controls class="audio-player"></audio> <button class="record-btn">錄音</button> <a id="download" download="record.aac"></a> </div> </body> <script src="js/record.js"></script> </html>
record.css:
.app { display: flex; justify-content: center; align-items: center; } .record-btn { margin: 0 10px; }
record.js:
const recordBtn = document.querySelector(".record-btn") const player = document.querySelector(".audio-player") const download = document.querySelector('#download') if (navigator.mediaDevices.getUserMedia) { let audioChunks = [] // 約束屬性 const constraints = { // 音頻約束 audio: { sampleRate: 16000, // 采樣率 sampleSize: 16, // 每個(gè)采樣點(diǎn)大小的位數(shù) channelCount: 1, // 通道數(shù) volume: 1, // 從 0(靜音)到 1(最大音量)取值,被用作每個(gè)樣本值的乘數(shù) echoCancellation: true, // 開(kāi)啟回音消除 noiseSuppression: true, // 開(kāi)啟降噪功能 }, // 視頻約束 video: false } // 請(qǐng)求獲取音頻流 navigator.mediaDevices.getUserMedia(constraints) .catch(err => serverLog("ERROR mediaDevices.getUserMedia: ${err}")) .then(stream => {// 在此處理音頻流 // 創(chuàng)建 MediaRecorder 實(shí)例 const mediaRecorder = new MediaRecorder(stream) // 點(diǎn)擊按鈕 recordBtn.onclick = () => { if (mediaRecorder.state === "recording") { // 錄制完成后停止 mediaRecorder.stop() recordBtn.textContent = "錄音結(jié)束" } else { // 開(kāi)始錄制 mediaRecorder.start() recordBtn.textContent = "錄音中..." } } mediaRecorder.ondataavailable = e => { audioChunks.push(e.data) } // 結(jié)束事件 mediaRecorder.onstop = e => { // 將錄制的數(shù)據(jù)組裝成 Blob(binary large object) 對(duì)象(一個(gè)不可修改的存儲(chǔ)二進(jìn)制數(shù)據(jù)的容器) const blob = new Blob(audioChunks, { type: "audio/aac" }) audioChunks = [] const audioURL = window.URL.createObjectURL(blob) // 賦值給一個(gè) <audio> 元素的 src 屬性進(jìn)行播放 player.src = audioURL // 添加下載功能 download.innerHTML = '下載' download.href = audioURL } }, () => { console.error("授權(quán)失?。?); } ); } else { console.error("該瀏覽器不支持 getUserMedia!"); }
運(yùn)行實(shí)例
打開(kāi) record.html,首先獲取麥克風(fēng)權(quán)限:
點(diǎn)擊“允許”。
頁(yè)面有一個(gè) audio-player 和一個(gè) buttom。
點(diǎn)擊“錄音”按鈕,就開(kāi)始錄音了。
再點(diǎn)一次按鈕,停止錄音,數(shù)據(jù)傳回給 audio-player,可以在網(wǎng)頁(yè)上播放錄音。
點(diǎn)擊“下載”,可以下載錄制的音頻。
PS:音頻文件名稱(chēng)設(shè)置為 record.aac,文件格式為 WebM,音頻格式為 opus,單聲道,采樣率 48kHz,位深 32bit。
參考資料
源碼下載
百度網(wǎng)盤(pán)下載地址
鏈接: https://pan.baidu.com/s/1VS--_5O7FlBUss0VAERn-Q?pwd=8s43
提取碼: 8s43
GitHub:Web-Record
到此這篇關(guān)于JavaScript+HTML 實(shí)現(xiàn)網(wǎng)頁(yè)錄制音頻與下載的文章就介紹到這了,更多相關(guān)js網(wǎng)頁(yè)錄制音頻內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Angular+Bootstrap+Spring Boot實(shí)現(xiàn)分頁(yè)功能實(shí)例代碼
這篇文章主要介紹了Angular+Bootstrap+Spring Boot實(shí)現(xiàn)分頁(yè)功能實(shí)例代碼,需要的朋友可以參考下2017-07-07如何讓easyui gridview 寬度自適應(yīng)窗口改變及fitColumns應(yīng)用
在使用Easyui GridView時(shí),如果要Gridview的寬度和窗口的寬度相同,只需要設(shè)置fitColumns: true即可,感興趣的你不要走開(kāi)啊,接下來(lái)為您詳細(xì)介紹2013-01-01用js實(shí)現(xiàn)圖片旋轉(zhuǎn)的兩種方案
這篇文章主要給大家介紹了關(guān)于用js實(shí)現(xiàn)圖片旋轉(zhuǎn)的兩種方案, 旋轉(zhuǎn)的效果就是根據(jù)鼠標(biāo)的的移動(dòng)距離來(lái)顯示不同的圖片,形成視覺(jué)差,仿佛就是在正真的旋轉(zhuǎn),需要的朋友可以參考下2023-07-07JS實(shí)現(xiàn)網(wǎng)頁(yè)Div層Clone拖拽效果
這篇文章主要介紹了JS實(shí)現(xiàn)網(wǎng)頁(yè)Div層Clone拖拽效果,涉及JavaScript響應(yīng)鼠標(biāo)事件動(dòng)態(tài)改變頁(yè)面元素位置屬性及層級(jí)屬性的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-09-09javascript和jQuery實(shí)現(xiàn)網(wǎng)頁(yè)實(shí)時(shí)聊天的ajax長(zhǎng)輪詢(xún)
在做網(wǎng)頁(yè)實(shí)時(shí)聊天的時(shí)候常常需要長(zhǎng)輪詢(xún),本文由于采用原生的JS及AJAX,所以簡(jiǎn)單易懂,通過(guò)這篇文章就可以建立一個(gè)簡(jiǎn)單的聊天室程序。2016-07-07JS選項(xiàng)卡動(dòng)態(tài)替換banner圖片路徑的方法
這篇文章主要介紹了JS選項(xiàng)卡動(dòng)態(tài)替換banner圖片路徑的方法,涉及javascript操作文件css樣式的相關(guān)技巧,非常簡(jiǎn)單實(shí)用,需要的朋友可以參考下2015-05-05JS實(shí)現(xiàn)頁(yè)面加載完成之后自動(dòng)刷新一次問(wèn)題
這篇文章主要介紹了JS實(shí)現(xiàn)頁(yè)面加載完成之后自動(dòng)刷新一次問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02微信小程序獲取地理位置及經(jīng)緯度授權(quán)代碼實(shí)例
這篇文章主要介紹了微信小程序獲取地理位置及經(jīng)緯度授權(quán)代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09