文章目錄

  • 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無效)。
  • 注意事項
  1. 單次授權:用户只需授權一次,後續調用通常不再彈窗(除非用户清除權限記錄)。
  2. 多標籤頁限制:同一瀏覽器中,只有一個標籤頁能獨佔麥克風。
  3. 釋放資源:使用完畢後調用 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()

Array<MediaStreamTrack>

僅音頻軌道(kind === “audio”)

stream.getTracks()

Array<MediaStreamTrack>

所有軌道(音頻 + 視頻 + 其他類型)

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