×

手把手教你如何使用“MediaStream API”录制音频

作者:Web前端之家2021.09.14来源:Web前端之家浏览:141评论:0
关键词:MediaStreamAPI
微信公众号

微信公众号

500.jpg

该媒体捕获和流API(又名MediaStream API)允许您记录从用户的麦克风声音,然后得到的录音或媒体元素的轨道。然后,您可以在录制后直接播放这些曲目,也可以将媒体上传到您的服务器。

在本教程中,我们将创建一个网站,该网站将使用 Media Streams API 来允许用户录制某些内容,然后将录制的音频上传到服务器进行保存。用户还可以查看和播放所有上传的录音。

设置服务器

我们将首先创建一个Node.js和Express服务器。因此,如果您的机器上没有Node.js,请首先确保下载并安装它。

创建目录

创建一个将保存项目的新目录,然后切换到该目录:

mkdir recording-tutorial
cd recording-tutorial

初始化项目

然后,使用 npm 初始化项目:

npm init -y

该选项使用默认值-y创建package.json

安装依赖项

接下来,我们将为我们正在创建的服务器安装 Express 并在发生任何更改时安装nodemon以重新启动服务器:

npm i express nodemon

创建 Express 服务器

我们现在可以从创建一个简单的服务器开始。index.js在项目根目录下创建,内容如下:

const path = require('path');
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;

app.use(express.static('public/assets'));

app.listen(port, () => {
  console.log(`App listening at http://localhost:${port}`);
});

这将创建一个将在端口上运行的服务器,3000除非在环境中设置了端口,并且它公开了一个目录public/assets——我们将很快创建——将保存 JavaScript 和 CSS 文件和图像。

添加脚本

最后,startscripts in下添加一个脚本package.json

"scripts": {
    "start": "nodemon index.js"
},

启动网络服务器

让我们测试我们的服务器。运行以下命令来启动服务器:

npm start

并且服务器应该从端口 3000 开始。您可以尝试在 上访问它localhost:3000,但是您将看到一条消息“无法 GET /”,因为我们还没有定义任何路由。

创建录制页面

接下来,我们将创建将成为网站主页的页面。用户将使用此页面来记录、查看和播放录音。

创建public目录,并在其中创建一个index.html包含以下内容的文件:


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Record</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet"
    integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We" crossorigin="anonymous">
  <link href="/css/index.css" rel="stylesheet" />
</head>
<body class="pt-5">
  <div class="container">
    <h1 class="text-center">Record Your Voice</h1>
    <div class="record-button-container text-center mt-5">
      <button class="bg-transparent border btn record-button rounded-circle shadow-sm text-center" id="recordButton">
        <img src="/images/microphone.png" alt="Record" class="img-fluid" />
      </button>
    </div>
  </div>
</body>
</html>

此页面使用Bootstrap 5进行样式设置。目前,该页面仅显示用户可用于录制的按钮。

请注意,我们为麦克风使用了图像。您可以在Iconscout上下载图标,也可以使用GitHub 存储库中的修改版本。

下载图标并将其放置public/assets/images在名称中microphone.png

添加样式

我们还链接了 stylesheet index.css,因此创建一个public/assets/css/index.css包含以下内容的文件:

.record-button {
    height: 8em;
    width: 8em;
    border-color: #f3f3f3 !important;
  }
  
  .record-button:hover {
    box-shadow: 0 .5rem 1rem rgba(0,0,0,.15)!important;
  }

创建路由

最后,我们只需要在index.js. 在之前添加以下内容app.listen

app.get('/', (req, res) => {
    res.sendFile(path.join(__dirname, 'public/index.html'));
});

如果服务器尚未运行,请使用npm start. 然后localhost:3000在浏览器中访问。你会看到一个记录按钮。

212.jpg


现在,按钮什么都不做。我们需要绑定一个点击事件来触发录音。

创建一个public/assets/js/record.js包含以下内容的文件:

//initialize elements we'll use
const recordButton = document.getElementById('recordButton');
const recordButtonImage = recordButton.firstElementChild;

let chunks = []; //will be used later to record audio
let mediaRecorder = null; //will be used later to record audio
let audioBlob = null; //the blob that will hold the recorded audio

我们正在初始化稍后将使用的变量。然后创建一个record函数,它将作为点击事件的监听器recordButton

function record() {
    //TODO start recording
}
  
recordButton.addEventListener('click', record);

我们还将此函数作为事件侦听器附加到记录按钮上。

媒体录制

为了开始录制,我们需要使用mediaDevices.getUserMedia()方法。

这种方法允许我们获取流并记录用户的音频和/或视频,只有在用户为网站提供权限后才能这样做。该getUserMedia方法允许我们访问本地输入设备。

getUserMedia接受MediaStreamConstraints对象作为参数,该对象包含一组约束,这些约束指定我们将从 中获得的流中的预期媒体类型getUserMedia。这些约束可以是带有布尔值的音频和视频。

如果值为 false,则表示我们对访问此设备或录制此媒体不感兴趣。

getUserMedia返回一个承诺。如果用户允许网站录制,promise 的履行处理程序会收到一个MediaStream对象,我们可以使用它来媒体捕获用户的视频或音频流。

媒体捕获和流

要使用 MediaStream API 对象来捕获媒体轨道,我们需要使用MediaRecorder接口。我们需要创建一个新的接口对象,该对象在构造函数中接受 MediaStream 对象,并允许我们通过其方法轻松控制录制。

record函数内部,添加以下内容:

//check if browser supports getUserMedia
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
  alert('Your browser does not support recording!');
  return;
}

// browser supports getUserMedia
// change image in button
recordButtonImage.src = `/images/${mediaRecorder && mediaRecorder.state === 'recording' ? 'microphone' : 'stop'}.png`;
if (!mediaRecorder) {
  // start recording
  navigator.mediaDevices.getUserMedia({
    audio: true,
  })
    .then((stream) => {
      mediaRecorder = new MediaRecorder(stream);
      mediaRecorder.start();
      mediaRecorder.ondataavailable = mediaRecorderDataAvailable;
      mediaRecorder.onstop = mediaRecorderStop;
    })
    .catch((err) => {
      alert(`The following error occurred: ${err}`);
      // change image in button
      recordButtonImage.src = '/images/microphone.png';
    });
} else {
  // stop recording
  mediaRecorder.stop();
}

浏览器支持

我们首先检查navigator.mediaDevices和是否navigator.mediaDevices.getUserMedia已定义,因为有些浏览器如 Internet Explorer、Android 上的 Chrome 或其他不支持它的浏览器。

此外,使用getUserMedia需要安全网站,这意味着页面要么使用 HTTPS 加载file://,要么来自localhost. 因此,如果页面未安全加载,mediaDevices并且getUserMedia将是未定义的。

开始录音

如果条件为假(即,都支持mediaDevicesgetUserMedia),我们首先将录制按钮的图像更改为stop.png,您可以从Iconscout或GitHub【https://github.com/sitepoint-editors/mediastream-tutorial/blob/master/public/assets/images/stop.png】存储库下载并将其放置在public/assets/images.

然后,我们检查mediaRecorder——我们在文件开头定义的——是否为空。

如果它为空,则表示没有正在进行的录制。因此,我们获得了一个 MediaStream 实例以开始使用getUserMedia.

我们向它传递一个只有 keyaudio和 value的对象true,因为我们只是在录制音频。

这是浏览器提示用户允许网站访问麦克风的地方。如果用户允许,将执行执行处理程序中的代码:

mediaRecorder = new MediaRecorder(stream);
mediaRecorder.start();
mediaRecorder.ondataavailable = mediaRecorderDataAvailable;
mediaRecorder.onstop = mediaRecorderStop;

在这里,我们正在创建一个 new MediaRecorder,并将其分配给mediaRecorder我们在文件开头定义的。

我们正在传递从 接收到的流的构造函数getUserMedia。然后,我们使用mediaRecorder.start()开始录制。

最后,我们将事件处理程序(我们将很快创建)绑定到两个事件,dataavailable以及stop.

我们还添加了一个catch处理程序,以防用户不允许网站访问麦克风或可能引发的任何其他异常。

停止录音

如果mediaRecorder不为空,这一切都会发生。如果它为空,则表示有正在进行的录制并且用户正在结束它。所以,我们使用mediaRecorder.stop()方法来停止录制:

} else {
    //stop recording
    mediaRecorder.stop();
}

处理媒体录制事件

到目前为止,我们的代码在用户单击录制按钮时开始和停止录制。接下来,我们将添加事件处理程序dataavailablestop

关于可用数据

dataavailable事件在完成完整记录时触发,或基于timeslice传递给的可选参数mediaRecorder.start()以指示应触发此事件的毫秒数。传递时间片允许对记录进行切片并以块的形式获取它。

创建mediaRecorderDataAvailable函数,该函数将dataavailable通过将接收到的BlobEvent参数中的Blob音轨添加到我们在文件开头定义的数组中来处理事件:chunks

function mediaRecorderDataAvailable(e) {
    chunks.push(e.data);
}

该块将是用户录音的音轨数组。

停止时

在创建mediaRecorderStop将处理停止事件的 之前,让我们首先添加 HTML 元素容器,该容器将使用Save和Discard按钮保存录制的音频。

public/index.html在结束</body>标记之前添加以下内容:

<div class="recorded-audio-container mt-5 d-none flex-column justify-content-center align-items-center"
  id="recordedAudioContainer">
  <div class="actions mt-3">
    <button class="btn btn-success rounded-pill" id="saveButton">Save</button>
    <button class="btn btn-danger rounded-pill" id="discardButton">Discard</button>
  </div>
</div>

然后,在 的开头public/assets/js/record.js添加一个变量,该变量将成为#recordedAudioContainer元素的 Node 实例:

const recordedAudioContainer = document.getElementById('recordedAudioContainer');

我们现在可以实现mediaRecorderStop. 该函数将首先删除任何先前录制但未保存的音频元素,创建一个新的音频媒体元素,将 设置src为录制流的 Blob,并显示容器:


function mediaRecorderStop () {
    //check if there are any previous recordings and remove them
    if (recordedAudioContainer.firstElementChild.tagName === 'AUDIO') {
      recordedAudioContainer.firstElementChild.remove();
    }
    //create a new audio element that will hold the recorded audio
    const audioElm = document.createElement('audio');
    audioElm.setAttribute('controls', ''); //add controls
    //create the Blob from the chunks
    audioBlob = new Blob(chunks, { type: 'audio/mp3' });
    const audioURL = window.URL.createObjectURL(audioBlob);
    audioElm.src = audioURL;
    //show audio
    recordedAudioContainer.insertBefore(audioElm, recordedAudioContainer.firstElementChild);
    recordedAudioContainer.classList.add('d-flex');
    recordedAudioContainer.classList.remove('d-none');
    //reset to default
    mediaRecorder = null;
    chunks = [];
}

最后,我们将重置mediaRecorderchunks恢复到它们的初始值以处理下一个录音。使用此代码,我们的网站应该能够录制音频,并且当用户停止时,它允许他们播放录制的音频。

我们需要做的最后一件事是链接到record.jsin index.htmlscript在 的末尾添加body

<script src="/js/record.js"></script>

测试录音

让我们现在看看。转到localhost:3000浏览器,然后单击录制按钮。系统会要求您允许该网站使用麦克风。

aaa.jpg

即使您使用受支持的浏览器,也请确保在本地主机或 HTTPS 服务器上加载网站。MediaDevices 和getUserMedia在其他条件下不可用。

单击“允许”。然后麦克风图像将更改为停止图像。此外,您应该会根据浏览器在地址栏中看到一个录音图标。这表明该麦克风当前正在被网站访问。

aaa.jpg

尝试记录几秒钟。然后点击停止按钮。按钮的图像将变回麦克风图像,音频播放器将显示两个按钮 — Save和Discard。

接下来,我们将实现Save和Discard按钮的点击事件。在保存按钮应该上传音频服务器,以及放弃按钮应该将其删除。

丢弃点击事件处理程序

我们将首先实现Discard按钮的事件处理程序。单击此按钮应首先向用户显示提示,以确认他们要放弃录音。然后,如果用户确认,它将移除音频播放器并隐藏按钮。

将保存Discard按钮的变量添加到 的开头public/assets/js/record.js

const discardAudioButton = document.getElementById('discardButton');

然后,将以下内容添加到文件末尾:

function discardRecording () {
    //show the user the prompt to confirm they want to discard
    if (confirm('Are you sure you want to discard the recording?')) {
      //discard audio just recorded
      resetRecording();
    }
  }
  
  function resetRecording () {
    if (recordedAudioContainer.firstElementChild.tagName === 'AUDIO') {
      //remove the audio
      recordedAudioContainer.firstElementChild.remove();
      //hide recordedAudioContainer
      recordedAudioContainer.classList.add('d-none');
      recordedAudioContainer.classList.remove('d-flex');
    }
    //reset audioBlob for the next recording
    audioBlob = null;
  }
  
  //add the event listener to the button
  discardAudioButton.addEventListener('click', discardRecording);

您现在可以尝试录制一些内容,然后单击“放弃”按钮。音频播放器将被移除并隐藏按钮。

上传到服务器

保存点击事件处理程序

现在,我们将为Save按钮实现点击处理程序。当用户单击“保存”按钮时,此处理程序将audioBlob使用Fetch API【https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API】将 上传到服务器。

让我们从uploads在项目根目录中创建一个目录开始:

mkdir uploads

然后,在 的开头record.js,添加一个将保存保存按钮元素的变量:

const saveAudioButton = document.getElementById('saveButton');

然后,在最后添加以下内容:

function saveRecording () {
    //the form data that will hold the Blob to upload
    const formData = new FormData();
    //add the Blob to formData
    formData.append('audio', audioBlob, 'recording.mp3');
    //send the request to the endpoint
    fetch('/record', {
      method: 'POST',
      body: formData
    })
    .then((response) => response.json())
    .then(() => {
      alert("Your recording is saved");
      //reset for next recording
      resetRecording();
      //TODO fetch recordings
    })
    .catch((err) => {
      console.error(err);
      alert("An error occurred, please try again later");
      //reset for next recording
      resetRecording();
    })
  }
  
  //add the event handler to the click event
  saveAudioButton.addEventListener('click', saveRecording);

请注意,一旦录音上传,我们将resetRecording用于重置下一个录音的音频。稍后,我们将获取所有录音以显示给用户。

创建 API 端点

我们现在需要实现 API 端点。端点将音频上传到uploads目录。

为了在 Express 中轻松处理文件上传,我们将使用库Multer。Multer 提供了一个中间件来处理文件上传。

运行以下命令进行安装:

npm i multer

然后,在 中index.js,将以下内容添加到文件的开头:

const fs = require('fs');
const multer = require('multer');

const storage = multer.diskStorage({
  destination(req, file, cb) {
    cb(null, 'uploads/');
  },
  filename(req, file, cb) {
    const fileNameArr = file.originalname.split('.');
    cb(null, `${Date.now()}.${fileNameArr[fileNameArr.length - 1]}`);
  },
});
const upload = multer({ storage });

我们声明了storageusing multer.diskStorage,我们将其配置为在uploads目录中存储文件,并且我们正在根据带有扩展名的当前时间戳保存文件。

然后,我们声明了upload,这将是上传文件的中间件。

接下来,我们要使uploads目录中的文件可公开访问。因此,在之前添加以下内容app.listen

app.use(express.static('uploads'));

最后,我们将创建上传端点。此端点将仅使用upload中间件上传音频并返回 JSON 响应:

app.post('/record', upload.single('audio'), (req, res) => res.json({ success: true }));

upload中间件将处理文件上传。我们只需要传递我们要上传到的文件的字段名称upload.single

请注意,通常情况下,您需要对文件执行验证并确保上传的是正确的、预期的文件类型。为简单起见,我们在本教程中省略了这一点。

测试上传

让我们来测试一下。localhost:3000再次转到浏览器,记录一些内容,然后单击“保存”按钮。

请求将被发送到端点,文件将被上传,并向用户显示警报以通知他们记录已保存。

您可以通过检查uploads项目根目录下的目录来确认音频确实已上传。您应该在那里找到一个 MP3 音频文件。

显示录音

创建 API 端点

我们要做的最后一件事是向用户显示所有录音,以便他们可以播放。

首先,我们将创建用于获取所有文件的端点。在app.listenin之前添加以下内容index.js

app.get('/recordings', (req, res) => {
    let files = fs.readdirSync(path.join(__dirname, 'uploads'));
    files = files.filter((file) => {
      // check that the files are audio files
      const fileNameArr = file.split('.');
      return fileNameArr[fileNameArr.length - 1] === 'mp3';
    }).map((file) => `/${file}`);
    return res.json({ success: true, files });
  });

我们只是读取uploads目录中的文件,过滤它们以仅获取mp3文件,并/在每个文件名后附加一个。最后,我们返回一个带有文件的 JSON 对象。

添加录音容器元素

接下来,我们将添加一个 HTML 元素,该元素将作为我们将显示的录音的容器。在record.js脚本前的正文末尾添加以下内容:

<h2 class="mt-3">Saved Recordings</h2>
<div class="recordings row" id="recordings">

</div>

从 API 获取文件

还要添加到record.js将保存#recordings元素的变量的开头:

const recordingsContainer = document.getElementById('recordings');

然后,我们将添加一个fetchRecordings函数,该函数将调用我们之前创建的端点,然后使用该createRecordingElement函数渲染将成为音频播放器的元素。

我们还将playRecording为将播放音频的按钮上的单击事件添加一个事件侦听器。

在末尾添加以下内容record.js

function fetchRecordings () {
    fetch('/recordings')
    .then((response) => response.json())
    .then((response) => {
      if (response.success && response.files) {
        //remove all previous recordings shown
        recordingsContainer.innerHTML = '';
        response.files.forEach((file) => {
          //create the recording element
          const recordingElement = createRecordingElement(file);
          //add it the the recordings container
          recordingsContainer.appendChild(recordingElement);
        })
      }
    })
    .catch((err) => console.error(err));
  }
  
  //create the recording element
  function createRecordingElement (file) {
    //container element
    const recordingElement = document.createElement('div');
    recordingElement.classList.add('col-lg-2', 'col', 'recording', 'mt-3');
    //audio element
    const audio = document.createElement('audio');
    audio.src = file;
    audio.onended = (e) => {
      //when the audio ends, change the image inside the button to play again
      e.target.nextElementSibling.firstElementChild.src = 'images/play.png';
    };
    recordingElement.appendChild(audio);
    //button element
    const playButton = document.createElement('button');
    playButton.classList.add('play-button', 'btn', 'border', 'shadow-sm', 'text-center', 'd-block', 'mx-auto');
    //image element inside button
    const playImage = document.createElement('img');
    playImage.src = '/images/play.png';
    playImage.classList.add('img-fluid');
    playButton.appendChild(playImage);
    //add event listener to the button to play the recording
    playButton.addEventListener('click', playRecording);
    recordingElement.appendChild(playButton);
    //return the container element
    return recordingElement;
  }
  
  function playRecording (e) {
    let button = e.target;
    if (button.tagName === 'IMG') {
      //get parent button
      button = button.parentElement;
    }
    //get audio sibling
    const audio = button.previousElementSibling;
    if (audio && audio.tagName === 'AUDIO') {
      if (audio.paused) {
        //if audio is paused, play it
        audio.play();
        //change the image inside the button to pause
        button.firstElementChild.src = 'images/pause.png';
      } else {
        //if audio is playing, pause it
        audio.pause();
        //change the image inside the button to play
        button.firstElementChild.src = 'images/play.png';
      }
    }
}

请注意,在playRecording函数内部,我们正在使用 来检查音频是否正在播放audio.paused,如果当前没有播放音频,它将返回 true。

我们还使用将显示在每个录音中的播放和暂停图标。您可以从Iconscout或GitHub 存储库【https://github.com/sitepoint-editors/mediastream-tutorial/tree/master/public/assets/images】获取这些图标。

我们将fetchRecordings在页面加载和新录音上传时使用。

因此,record.js在执行处理程序的末尾和内部调用该函数saveRecording来代替TODO注释:

.then(() => {
    alert("Your recording is saved");
    //reset for next recording
    resetRecording();
    //fetch recordings
    fetchRecordings();
  })

添加样式

我们需要做的最后一件事是为我们正在创建的元素添加一些样式。将以下内容添加到public/assets/css/index.css

.play-button:hover {
    box-shadow: 0 .5rem 1rem rgba(0,0,0,.15)!important;
  }
  
  .play-button {
    height: 8em;
    width: 8em;
    background-color: #5084d2;
  }

测试

现在一切准备就绪。localhost:3000在浏览器中打开网站,如果您之前上传过任何录音,现在您会看到它们。您也可以尝试上传新的并查看列表更新。

用户现在可以录制他们的声音、保存或丢弃它们。用户还可以查看所有上传的录音并播放它们。

结论

使用 MediaStream API 允许我们为用户添加媒体功能,例如录制音频。MediaStream Web API 还允许录制视频、截取屏幕截图等。

温馨提示:本文作者系Web前端之家 ,经Web前端之家编辑修改或补充,转载请注明出处和本文链接:
https://www.jiangweishan.com/article/MediaStreamAPI20210914.html

网友评论文明上网理性发言 已有0人参与

发表评论:

最新留言

  • 访客

    什么机翻...

  • Web前端之家

    只是更轻便,还是有些差距的呢,小项目可以玩玩。...

  • ja124

    这个跟vue和react比,好些吗?...

  • Web前端之家

    OK,已经更新,谢谢!...

  • 韩涛博客

    韩涛博客的域名更换为www.hantaosec.com啦,请更新一下域名哈,谢谢。我还会继续链着咱们...

  • Web前端之家

    请持续关注Web前端之家。...

  • Y4K

    谢谢指导,这下背景问题解决了!...

  • Web前端之家

    要用起来才可以....

首页|JavaScript|HTML|HTML4|HTML5|CSS3|开发工具|性能优化|移动开发|前端教程|性能优化|开发工具|酷站欣赏|UI设计|前端教程

Copyright © 2021 Web前端之家(www.jiangweishan.com) 版权所有 All Rights Reserved.
粤ICP备12067512号-1

Copyright Your WebSite.Some Rights Reserved.

Powered By Z-BlogPHP 1.6.8 Valyria