文章目錄
- 1. 麥克風錄取音頻
- 1.1 麥克風權限獲取
- 1.1.1 MediaStream 對象 API &屬性
- 1.1.2 多次調用getUserMedia獲取權限
- 1.2 麥克風停止使用,避免佔用資源
- 1.3 麥克風使用
- 1.3.2 直接播放麥克風輸入
- 1.3.3 使用 Web Audio API 處理音頻
- 1.3.3 錄音(結合 MediaRecorder)
- 1.3 完整demo
- 1.4 多音頻源的可行性
- 1.4.1 直接混合到輸出
- 1.4.2 通過混音節點(GainNode)控制
- 1.5 getAudioTracks和 getTracks 對比
- 1.6 其他常用用法demo
- 1.6.1 基礎用法
- 1.6.2 結合權限API(更精準控制)
- 1.6.3
1. 麥克風錄取音頻
1.1 麥克風權限獲取
在前端獲取麥克風權限主要使用瀏覽器的 MediaDevices API,以下是詳細步驟和代碼示例:
// 請求麥克風權限
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 1.請求麥克風權限
navigator.mediaDevices.getUserMedia({audio: true})
.then(function (stream) {
console.log("麥克風權限已授予");
// 可以在這裏使用音頻流(stream)
console.log("=========before", stream)
sleep(1000)
stream.getTracks().forEach(track => track.stop()); // 關閉音頻流
console.log("=========after", stream)
}).catch(function (error) {
console.error("獲取麥克風權限失敗:", error);
/*
* NotAllowedError 用户拒絕權限
* NotFoundError 沒有麥克風設備
* NotReadableError 設備被佔用
* */
});
//2.使用 navigator.permissions.query() 檢查當前權限狀態
navigator.permissions.query({name: 'microphone'})
.then(result => {
console.log("麥克風權限狀態:", result.state); // 'granted', 'denied', 或 'prompt'
/**
* prompt 表示用户尚未明確允許或拒絕訪問麥克風
* granted 表示同意
* denied 表示拒絕
*/
});
//3.關閉麥克風
// stream.getTracks().forEach(track => track.stop()); // 關閉音頻流
{ audio: true }:請求音頻權限(麥克風)。
如果需要同時請求攝像頭,傳入{ audio: true, video: true }。
- 安全要求
HTTPS 或本地環境:現代瀏覽器要求頁面通過https://或http://localhost運行,否則會拒絕權限請求。
用户手勢觸發:必須在用户交互(如點擊按鈕)後調用,否則可能失敗.
- 返回結果解析
成功時返回的
MediaStream對象(僅僅屬於源,和輸出源沒有關係)
如果用户允許麥克風權限,Promise 解析為包含音頻軌道的 MediaStream:
MediaStream 的關鍵屬性和方法:
id:流的唯一標識符。active:布爾值,表示流是否處於活動狀態。getAudioTracks():返回所有音頻軌道的數組(例如麥克風輸入的音頻)。如果用户拒絕權限或麥克風不可用,Promise 會拒絕並返回一個 DOMException 錯誤:
錯誤名稱 (error.name) 觸發條件
NotAllowedError用户拒絕權限或瀏覽器策略禁止訪問。NotFoundError沒有可用的麥克風設備。NotReadableError麥克風被其他應用佔用或硬件故障。OverconstrainedError無法滿足請求的參數(如特定設備ID無效)。
- 注意事項
- 單次授權:用户只需授權一次,後續調用通常不再彈窗(除非用户清除權限記錄)。
- 多標籤頁限制:同一瀏覽器中,只有一個標籤頁能獨佔麥克風。
- 釋放資源:使用完畢後調用 track.stop() 或關閉流,避免佔用設備。
通過 MediaStream 對象,你可以直接處理原始音頻數據或將其傳遞給 Web Audio API、錄音庫(如 MediaRecorder)等進一步操作。
1.1.1 MediaStream 對象 API &屬性
MediaStream是瀏覽器提供的用於處理媒體流(如音頻、視頻)的核心對象。以下是關於其使用方法和唯一性的詳細説明:
MediaStream 的基本使用
關鍵方法
|
方法
|
作用
|
示例
|
|
getAudioTracks()
|
獲取所有音頻軌道
|
stream.getAudioTracks()
|
|
getVideoTracks()
|
獲取所有視頻軌道
|
stream.getVideoTracks()
|
|
getTracks()
|
獲取所有軌道(音頻+視頻)
|
stream.getTracks()
|
|
addTrack(track)
|
添加新軌道到流中
|
stream.addTrack(newAudioTrack)
|
|
removeTrack(track)
|
從流中移除軌道
|
stream.removeTrack(oldTrack)
|
關鍵屬性
|
屬性
|
説明
|
|
id
|
流的唯一標識符(字符串)
|
|
active
|
布爾值,表示流是否處於活動狀態
|
MediaStream 的唯一性
每次調用 getUserMedia() 會生成新的 MediaStream 即使同一設備,每次調用都會返回不同的流對象(id 不同):
const stream1 = await navigator.mediaDevices.getUserMedia({ audio: true });
const stream2 = await navigator.mediaDevices.getUserMedia({ audio: true });
console.log(stream1.id === stream2.id); // false
但底層設備是共享的 如果兩個流來自同一麥克風,音頻數據是相同的(硬件層面共享)。
同一麥克風可以被多個 MediaStream 共享,但硬件性能有限(可能增加延遲)。
1.1.2 多次調用getUserMedia獲取權限
- 首次調用 getUserMedia(無權限時)
行為:瀏覽器會彈出權限請求對話框,用户必須明確選擇 允許 或 拒絕。
結果:
* 用户點擊 允許 → 返回 MediaStream,同時瀏覽器記錄權限為 granted。
* 用户點擊 拒絕 → 拋出 NotAllowedError 錯誤,後續調用也會直接失敗(除非用户手動修改權限)。
- 後續調用 getUserMedia(已有權限時)
同頁面會話內:直接返回 MediaStream,不會重複彈窗(權限已被緩存)。
跨頁面/會話:取決於瀏覽器策略:
* Chrome/Firefox:通常記憶權限,自動授予。
* Safari:可能重新彈窗(尤其是跨會話時)。
1.2 麥克風停止使用,避免佔用資源
- 手動釋放資源 必須顯式停止軌道,否則麥克風會持續佔用:
stream.getTracks().forEach(track => track.stop()); // 關閉音頻流
- 自動釋放條件
頁面關閉時瀏覽器會自動釋放。
流對象不再被引用時可能被垃圾回收(但顯式釋放更可靠)。
1.3 麥克風使用
1.3.2 直接播放麥克風輸入
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const audio = new Audio();
audio.srcObject = stream; // 將流綁定到 <audio> 元素
audio.play(); // 播放(注意:可能產生回聲)
1.3.3 使用 Web Audio API 處理音頻
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const audioContext = new AudioContext();
const source = audioContext.createMediaStreamSource(stream);
source.connect(audioContext.destination); // 輸出到揚聲器
1.3.3 錄音(結合 MediaRecorder)
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const mediaRecorder = new MediaRecorder(stream);
let chunks = [];
mediaRecorder.ondataavailable = (e) => chunks.push(e.data);
mediaRecorder.onstop = () => {
const audioBlob = new Blob(chunks, { type: 'audio/wav' });
const audioUrl = URL.createObjectURL(audioBlob);
//debug停住可以使用這個鏈接聽
console.log("錄音文件URL:", audioUrl);
};
mediaRecorder.start();
setTimeout(() => mediaRecorder.stop(), 5000); // 5秒後停止
1.3 完整demo
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button id="startMic">啓用麥克風</button>
<script>
document.getElementById('startMic').addEventListener('click', async function () {
try {
const stream = await navigator.mediaDevices.getUserMedia({audio: true});
console.log("麥克風已啓用");
//stream包含來自麥克風的實時音頻軌道(MediaStreamTrack)。
// 示例:將音頻流連接到音頻上下文
/**
* 作用:初始化Web Audio API的音頻處理上下文,所有音頻操作(播放、分析、效果等)均在此環境中進行。
* 注意:某些瀏覽器(如iOS Safari)要求音頻上下文必須在用户交互(如點擊)後創建,否則會處於掛起狀態(需調用audioContext.resume())。
*/
const audioContext = new AudioContext();
/**
* 將麥克風流轉換為音頻源
* 作用:將MediaStream(麥克風輸入)轉換為Web Audio API可處理的音頻源節點(MediaStreamAudioSourceNode)。
* 關鍵點:此時音頻數據已從麥克風流入Web Audio處理管道,但尚未輸出。
*/
const source = audioContext.createMediaStreamSource(stream);
/**
* 作用:將音頻源節點連接到音頻輸出目標(通常是系統揚聲器)。
* audioContext.destination代表默認的音頻輸出設備。
* 效果:麥克風的聲音會實時從揚聲器播放,形成監聽迴路(可能產生嘯叫,需注意避免)。
*/
source.connect(audioContext.destination);
/**
*
* flowchart LR
* A[麥克風] -->|MediaStream| B[createMediaStreamSource]
* B --> C[MediaStreamAudioSourceNode]
* C -->|connect| D[AudioContext.destination]
* D --> E[揚聲器輸出]
*
*/
} catch (error) {
console.error("錯誤:", error);
}
});
</script>
</body>
</html>
1.4 多音頻源的可行性
Web Audio API的設計:支持多源輸入,所有音頻源最終會混合到AudioContext.destination(系統輸出)。
1.4.1 直接混合到輸出
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button id="startMic">啓用麥克風</button>
<script>
document.getElementById('startMic').addEventListener('click', async function () {
try {
const stream = await navigator.mediaDevices.getUserMedia({audio: true});
console.log("麥克風已啓用");
//stream包含來自麥克風的實時音頻軌道(MediaStreamTrack)。
// 麥克風 + 音頻文件同時播放 =>麥克風聲音和背景音樂混合輸出。
//麥克風源
const audioContext = new AudioContext();
const source = audioContext.createMediaStreamSource(stream);
source.connect(audioContext.destination);
// 音頻文件源
const response = await fetch('./test.mp3');
const arrayBuffer = await response.arrayBuffer();
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
const fileSource = audioContext.createBufferSource();
fileSource.buffer = audioBuffer;
fileSource.connect(audioContext.destination); // 同樣連接到揚聲器
fileSource.start();
// 麥克風聲音和背景音樂混合輸出。
} catch (error) {
console.error("錯誤:", error);
}
});
</script>
</body>
</html>
1.4.2 通過混音節點(GainNode)控制
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button id="startMic">啓用麥克風</button>
<script>
document.getElementById('startMic').addEventListener('click', async function () {
try {
const stream = await navigator.mediaDevices.getUserMedia({audio: true});
console.log("麥克風已啓用");
//stream包含來自麥克風的實時音頻軌道(MediaStreamTrack)。
// 麥克風 + 音頻文件同時播放 =>通過混音節點(GainNode)控制
// 創建混音節點
const audioContext = new AudioContext();
const mixer = audioContext.createGain();
mixer.connect(audioContext.destination);
//麥克風源
// 麥克風源(音量減半)
const source = audioContext.createMediaStreamSource(stream);
source.connect(mixer);
mixer.gain.value = 0.5; // 控制整體音量
// 音頻文件源
const response = await fetch('./test.mp3');
const arrayBuffer = await response.arrayBuffer();
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
const fileSource = audioContext.createBufferSource();
fileSource.buffer = audioBuffer;
fileSource.connect(mixer);
fileSource.start();
} catch (error) {
console.error("錯誤:", error);
}
});
</script>
</body>
</html>
1.5 getAudioTracks和 getTracks 對比
stream.getAudioTracks() 和 stream.getTracks() 都是用於獲取 MediaStream 中的軌道(tracks)的方法,但它們的返回結果不完全相同。以下是兩者的核心區別和適用場景:
|
方法
|
返回類型
|
包含的軌道
|
|
stream.getAudioTracks()
|
|
僅音頻軌道(kind === “audio”)
|
|
stream.getTracks()
|
|
所有軌道(音頻 + 視頻 + 其他類型)
|
const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
// 方法1:僅獲取音頻軌道
const audioTracks = stream.getAudioTracks();
console.log(audioTracks); // [MediaStreamTrack { kind: "audio", ... }]
// 方法2:獲取所有軌道(音頻和視頻)
const allTracks = stream.getTracks();
console.log(allTracks); // [MediaStreamTrack { kind: "audio", ... }, MediaStreamTrack { kind: "video", ... }]
1.6 其他常用用法demo
1.6.1 基礎用法
async function getMicrophone() {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
console.log("已獲取麥克風權限,Stream ID:", stream.id);
return stream;
} catch (error) {
console.error("獲取失敗:", error.name);
return null;
}
}
// 第一次調用:可能彈窗
const stream1 = await getMicrophone();
// 第二次調用(同頁面):直接返回流(如果第一次成功)
const stream2 = await getMicrophone();
1.6.2 結合權限API(更精準控制)
async function checkOrRequestMicrophone() {
// 檢查當前權限狀態(部分瀏覽器支持)
const permission = await navigator.permissions.query({ name: 'microphone' });
if (permission.state === 'granted') {
return navigator.mediaDevices.getUserMedia({ audio: true }); // 直接獲取
} else {
// 無權限時,需用户交互(如按鈕點擊)才能觸發彈窗
document.getElementById('micButton').addEventListener('click', async () => {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
console.log("用户已授權");
});
}
}
1.6.3