文章目录
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() | 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("用户已授权");
});
}
}
871

被折叠的 条评论
为什么被折叠?



