正在录制用户的音频

现在,许多浏览器都能够访问用户的视频和音频输入。不过,具体取决于浏览器,它可能是完全动态的内嵌体验,也可能委托给用户设备上的其他应用。

从简单开始,逐步

最简单的方法是直接要求用户提供预先录制的文件。为此,您可以创建一个简单的文件输入元素,并添加一个 accept 过滤器(表示我们只能接受音频文件)和一个 capture 属性(表示我们希望直接从麦克风获取音频)。

<input type="file" accept="audio/*" capture />

此方法适用于所有平台。在桌面设备上,它会提示用户从文件系统上传文件(忽略 capture 属性)。在 iOS 上的 Safari 中,它会打开麦克风应用,让您录制音频,然后将其发送回网页;在 Android 上,它会为用户提供选择,让用户选择使用哪个应用录制音频,然后再将其发送回网页。

用户完成录制并返回网站后,您需要以某种方式获取文件数据。您可以通过将 onchange 事件附加到输入元素,然后读取事件对象的 files 属性来快速访问。

<input type="file" accept="audio/*" capture id="recorder" />
<audio id="player" controls></audio>
  <script>
    const recorder = document.getElementById('recorder');
    const player = document.getElementById('player');

    recorder.addEventListener('change', function (e) {
      const file = e.target.files[0];
      const url = URL.createObjectURL(file);
      // Do something with the audio file.
      player.src = url;
    });
  </script>
</audio>

获得文件访问权限后,您就可以对文件执行任何操作。例如,您可以:

  • 直接将其附加到 <audio> 元素,以便播放
  • 将其下载到用户的设备
  • 通过将文件附加到 XMLHttpRequest,将其上传到服务器
  • 通过 Web Audio API 传递音频并对其应用过滤器

虽然使用输入元素方法获取音频数据很普遍,但这是最不吸引人的选项。我们确实希望获得麦克风访问权限,并在页面中直接提供良好的体验。

以互动方式使用麦克风

现代浏览器可以直接连接到麦克风,使我们能够构建与网页完全集成的体验,用户永远不会离开浏览器。

获取麦克风访问权限

我们可以使用 WebRTC 规范中名为 getUserMedia() 的 API 直接访问麦克风。getUserMedia() 会提示用户授予对其连接的麦克风和摄像头的访问权限。

如果成功,API 将返回一个 Stream,其中包含来自摄像头或麦克风的数据,然后我们可以将其附加到 <audio> 元素、WebRTC 流、Web Audio AudioContext,或使用 MediaRecorder API 保存。

如需从麦克风获取数据,我们只需在传递给 getUserMedia() API 的 constraints 对象中设置 audio: true。

<audio id="player" controls></audio>
<script>
  const player = document.getElementById('player');

  const handleSuccess = function (stream) {
    if (window.URL) {
      player.srcObject = stream;
    } else {
      player.src = stream;
    }
  };

  navigator.mediaDevices
    .getUserMedia({audio: true, video: false})
    .then(handleSuccess);
</script>

如果您想选择特定麦克风,可以先枚举可用的麦克风。

navigator.mediaDevices.enumerateDevices().then((devices) => {
  devices = devices.filter((d) => d.kind === 'audioinput');
});

然后,您可以在调用 getUserMedia 时传递要使用的 deviceId。

navigator.mediaDevices.getUserMedia({
  audio: {
    deviceId: devices[0].deviceId,
  },
});

就其本身而言,这并不是很有用。我们只能获取音频数据并进行播放。

访问麦克风中的原始数据

如需访问麦克风中的原始数据,我们必须获取由 getUserMedia() 创建的数据流,然后使用 Web Audio API 处理数据。Web Audio API 是一个简单的 API,它接受输入源,并将这些源连接到可以处理音频数据(调整增益等)的节点,最终连接到扬声器,以便用户可以听到。

您可以连接的节点之一是 AudioWorkletNode。此节点为您提供自定义音频处理的底层功能。实际的音频处理发生在 AudioWorkletProcessor 中的 process() 回调方法中。调用此函数可提供输入和参数并提取输出。

如需了解详情,请参阅进入 Audio Worklet。

<script>
  const handleSuccess = async function(stream) {
    const context = new AudioContext();
    const source = context.createMediaStreamSource(stream);

    await context.audioWorklet.addModule("processor.js");
    const worklet = new AudioWorkletNode(context, "worklet-processor");

    source.connect(worklet);
    worklet.connect(context.destination);
  };

  navigator.mediaDevices.getUserMedia({ audio: true, video: false })
      .then(handleSuccess);
</script>
// processor.js
class WorkletProcessor extends AudioWorkletProcessor {
  process(inputs, outputs, parameters) {
    // Do something with the data, e.g. convert it to WAV
    console.log(inputs);
    return true;
  }
}

registerProcessor("worklet-processor", WorkletProcessor);

缓冲区中保存的数据是来自麦克风的原始数据,您可以对这些数据执行多种操作:

  • 直接上传到服务器
  • 在本地存储
  • 将其转换为专用文件格式(例如 WAV),然后保存到服务器或本地

保存麦克风中的数据

保存麦克风数据最简单的方法是使用 MediaRecorder API。

MediaRecorder API 将获取 getUserMedia 创建的流,然后逐步将流中的数据保存到您的首选目标位置。

<a id="download">Download</a>
<button id="stop">Stop</button>
<script>
  const downloadLink = document.getElementById('download');
  const stopButton = document.getElementById('stop');


  const handleSuccess = function(stream) {
    const options = {mimeType: 'audio/webm'};
    const recordedChunks = [];
    const mediaRecorder = new MediaRecorder(stream, options);

    mediaRecorder.addEventListener('dataavailable', function(e) {
      if (e.data.size > 0) recordedChunks.push(e.data);
    });

    mediaRecorder.addEventListener('stop', function() {
      downloadLink.href = URL.createObjectURL(new Blob(recordedChunks));
      downloadLink.download = 'acetest.wav';
    });

    stopButton.addEventListener('click', function() {
      mediaRecorder.stop();
    });

    mediaRecorder.start();
  };

  navigator.mediaDevices.getUserMedia({ audio: true, video: false })
      .then(handleSuccess);
</script>

在本例中,我们将数据直接保存到一个数组中,稍后可以将其转换为 Blob,然后用于将数据保存到我们的 Web 服务器或直接保存到用户设备上的存储空间。

以负责任的方式请求使用麦克风的权限

如果用户之前未向您的网站授予麦克风访问权限,那么在您调用 getUserMedia 的瞬间,浏览器会提示用户向您的网站授予麦克风访问权限。

用户讨厌系统提示他们访问其机器上的强大设备,并且经常会屏蔽请求,或者如果他们不了解创建提示的上下文,则会忽略该请求。最佳实践是仅在首次需要时请求访问麦克风。用户授予访问权限后,系统不会再次询问,但如果用户拒绝访问,您将无法再次请求用户授予权限。

使用 Permissions API 检查您是否已拥有访问权限

getUserMedia API 不会告知您是否已拥有麦克风访问权限。这会给您带来一个问题:为了提供一个让用户授予您麦克风访问权限的良好界面,您必须请求麦克风访问权限。

在某些浏览器中,可以使用 Permission API 来解决此问题。借助 navigator.permission API,您可以查询访问特定 API 的能力状态,而无需再次提示。

如需查询您是否有权使用用户的麦克风,您可以将 {name: 'microphone'} 传入查询方法,该方法将返回以下任一值:

  • granted - 用户之前已向您授予麦克风访问权限;
  • prompt - 用户尚未向您授予访问权限,当您调用 getUserMedia 时,系统会提示用户授予访问权限;
  • denied - 系统或用户已明确禁止访问麦克风,您将无法访问麦克风。

现在,您可以快速检查是否需要更改界面,以适应用户需要执行的操作。

navigator.permissions.query({name: 'microphone'}).then(function (result) {
  if (result.state == 'granted') {
  } else if (result.state == 'prompt') {
  } else if (result.state == 'denied') {
  }
  result.onchange = function () {};
});

反馈