【前端】黑马JS-2

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

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值