在网络上,我们可以从用户的相机、麦克风甚至桌面捕获媒体流。我们可以使用这些媒体流通过 WebRTC 进行实时视频聊天,通过 MediaRecorder API 我们还可以直接在浏览器中记录和保存用户的音频或视频。
下面我们使用 HTML、CSS 和 JavaScript 构建一个简单的录音机应用来了解一下 MediaRecorder API 。在撰写本文时,支持的浏览器包括 Firefox,Chrome 和Opera。Edge 和 Safari 后续也会支持。
我们先来创建一个文件夹,再创建一个 HTML 文件及 CSS 文件供我们使用。
开始
要构建此应用,我们只需要一个文本编辑器和一个支持 MediaRecorded API 的浏览器。我们将 CSS 文件命名为 web-recorder-style.css 。
HTML 代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Web Recorder</title>
<link rel="stylesheet" href="./web-recorder-style.css" />
</head>
<body>
<header>
<h1>Web Recorder</h1>
</header>
<main>
<div class="controls">
<button type="button" id="mic">Get Microphone</button>
<button type="button" id="record" hidden>Record</button>
</div>
<ul id="recordings"></ul>
</main>
<script></script>
</body>
</html>
CSS 代码:
*{box-sizing: border-box;}
html,
body{min-height: 100vh; margin: 0; padding: 0;}
body{font-family: Helvetica, Arial, sans-serif; color: #0d122b; display: flex; flex-direction: column; padding-left: 1em; padding-right: 1em;}
h1{text-align: center; font-weight: 100;}
header{border-bottom: 1px solid #0d122b; margin-bottom: 2em;}
main{flex-grow: 2;}
.controls{text-align: center;}
button{font-size: 18px; font-weight: 200; padding: 1em; width: 200px; background: transparent; border: 4px solid #f22f46; border-radius: 4px; transition: all 0.4s ease 0s; cursor: pointer; color: #f22f46; margin-bottom: 2em;}
button:hover,
button:focus{background: #f22f46; color: #fff;}
#recordings{list-style-type: none; text-align: center; padding: 0; max-width: 600px; margin: 0 auto;}
#recordings li{display: flex; flex-direction: column; margin-bottom: 1em;}
#recordings audio{border-radius: 4px; margin: 0 auto 0.5em;}
a{color: #0d122b;}
.error{color: #f22f46; text-align: center;}
在浏览器打开页面你会看到如下内容:
现在让我们来看看 MediaRecorder API。
MediaRecorder API
在使用 MediaRecorder API 时需要有媒体流。我们可以从 <video> 或 <audio> 中获取,也可以通过调用 getUserMedia 来捕获用户的摄像头和麦克风。一旦获得流信息,便可以初始化 MediaRecorder 。
在录音过程中,MediaRecorder 会广播 dataavailable 事件,并将记录的数据作为事件的一部分。我们可以监听这些事件并将数据块存放在数组中。录音完成后,可以将数组里的数据合成 Blob 对象。通过调用 MediaRecorder 对象上的 start 和 stop 来控制录制的开始和结束。
下面,就让我们来实践一下。
getUserMedia
首先,我们与页面结合起来,通过按钮来获取用户的麦克风媒体流。在页面 <script> 标签里写上以下内容
window.addEventListener('DOMContentLoaded', () => {
const getMic = document.getElementById('mic');
const recordButton = document.getElementById('record');
const list = document.getElementById('recordings');
});
接下来,我们要检查浏览器是否支持。如果不支持,我们在页面显示提示信息。
window.addEventListener('DOMContentLoaded', () => {
const getMic = document.getElementById('mic');
const recordButton = document.getElementById('record');
const list = document.getElementById('recordings');
if ('MediaRecorder' in window) {
// everything is good, let's go ahead
} else {
renderError("Sorry, your browser doesn't support the MediaRecorder API, so this demo will not work.");
}
});
在事件之后,我们添加 renderErro 方法。
function renderError(message) {
const main = document.querySelector('main');
main.innerHTML = `<div class="error"><p>${message}</p></div>`;
}
如果可以访问 MediaRecorder ,那么我们需要麦克风权限来进行录制。这里会用到 getUserMedia API。我们不会直接请求麦克风,而是由用户点击按钮来使用麦克风并进行授权。
if ('MediaRecorder' in window) {
getMic.addEventListener('click', async () => {
getMic.setAttribute('hidden', 'hidden');
try {
const stream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: false
});
console.log(stream);
} catch {
renderError(
'You denied access to the microphone so this demo will not work.'
);
}
});
} else {
调用 navigator.mediaDevices.getUserMedia 将返回一个 promise ,如果用户允许访问,我们便可以成功获取媒体流。
用户可能会禁止访问麦克风,我们使用 try/catch 处理异常。如果用户禁止则会执行 catch 里的 renderError 方法。
保存文件并在浏览器里打开。点击「Get Microphone」按钮。会显示是否允许使用麦克风的提示,允许后在控件台会看到打印出的 MediaStream 信息。
录音
现在我们获得了麦克风的使用权限,下来我们要定义一些变量供后面使用。首先,MIME 类型使用 auto/webm ,这个类型被浏览器广泛支持。我们还需要创建一个名为 chunks 的数组用来保存录音中的数据。
MediaRecorder 通过用户麦克风获取的媒体流及我们定义的 MIME 类型选项进行初始化。我们把之前的 console.log 替换掉:
try {
const stream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: false
});
const mimeType = 'audio/webm';
let chunks = [];
const recorder = new MediaRecorder(stream, { type: mimeType });
我们为创建好的 MediaRecorder 设置一些监听事件。录音机会广播很多不同的事件,很多与它本身的交互有关,所以我们可以监听到开始、暂停、恢复和停止的事件。最主要的事件是 dataavailable,它会在录音过程中定期广播,这个事件中包含一段录音,我们可以把他存到定义好的 chunks 数组中。
const recorder = new MediaRecorder(stream, { type: mimeType });
recorder.addEventListener('dataavailable', event => {
if (typeof event.data === 'undefined') return;
if (event.data.size === 0) return;
chunks.push(event.data);
});
recorder.addEventListener('stop', () => {
const recording = new Blob(chunks, {
type: mimeType
});
renderRecording(recording, list);
chunks = [];
});
我们很快就会实现 renderRecording 方法。现在我们需要实现按钮的开始和停止。
我们需要取消隐藏录制按钮,然后在点击时启动或停止录制,具体取决于录音机的状态。代码如下:
chunks = [];
});
recordButton.removeAttribute('hidden');
recordButton.addEventListener('click', () => {
if (recorder.state === 'inactive') {
recorder.start();
recordButton.innerText = 'Stop';
} else {
recorder.stop();
recordButton.innerText = 'Record';
}
});
我们将把录音在 <audio> 元素上呈现并提供下载链接,以便用户可以将他们的录音下载下来。我们可以使用 URL.createObjectUrl 方法将 Blob 转成 URL。转成的 URL 可以做为 <audio> 的 src 及锚点元素的 href 使用。为保证文件能够下载,我们设置 download 属性。
renderRecording 方法主要创建 DOM 元素及在录制时创建文件名。我们将它放到 renderError 方法下面。
function renderRecording(blob, list) {
const blobUrl = URL.createObjectURL(blob);
const li = document.createElement('li');
const audio = document.createElement('audio');
const anchor = document.createElement('a');
anchor.setAttribute('href', blobUrl);
const now = new Date();
anchor.setAttribute(
'download',
`recording-${now.getFullYear()}-${(now.getMonth() + 1).toString().padStart(2, '0')}-${now.getDay().toString().padStart(2, '0')}--${now.getHours().toString().padStart(2, '0')}-${now.getMinutes().toString().padStart(2, '0')}-${now.getSeconds().toString().padStart(2, '0')}.webm`
);
anchor.innerText = 'Download';
audio.setAttribute('src', blobUrl);
audio.setAttribute('controls', 'controls');
li.appendChild(audio);
li.appendChild(anchor);
list.appendChild(li);
}
测试
在浏览器中打开页面并点击「Get Microphone」按钮。在权限对话框点击「允许」,然后点击「Record」。自己录一段信息并播放。
WebM 文件
如果您下载了其中一个录音,您可能会发现没有能够播放 WebM 文件的媒体播放器。WebM 是音频和视频的开源格式,它主要被浏览器所支持。如果你有 VLC 播放器便可以播放,不然的话你就需要将它转成 MP3 或 WAV 文件。
现在你的浏览器变成录音机了
MediaRecorder API 是浏览器新增的强大功能。在本文我们已经看到了它的录音能力,但它不仅于此。我们的应用没有保存功能,刷新页面后录音会丢失。我们可以使用 IndexedDb 或发送到服务器保存。如果 WebM 不是你想要的格式,可以考虑在前端进行重新编码,尽管这可能是 WebAssembly 的工作...
这里有一个 live demo。Github 源码在这里。
作者:Phil Nash
译者:Mark Wong