Как записывать аудио с помощью MediaStream API?

Как записывать аудио с помощью MediaStream API Изучение

Захват и Streams API медиа (MediaStream API) позволяет записывать звук с микрофона пользователя, а затем получить записанные аудио или мультимедийные элементы, как дорожки. Затем вы можете либо воспроизвести эти треки сразу после их записи, либо загрузить носитель на свой сервер.

В этом руководстве мы создадим веб-сайт, который будет использовать API Media Streams, чтобы позволить пользователю что-то записывать, а затем загружать записанный звук на сервер для сохранения. Пользователь также сможет просматривать и воспроизводить все загруженные записи.

Вы можете найти полный код этого руководства в этом репозитории GitHub.

Настройка сервера

Сначала мы начнем с создания сервера Node.js и Express. Поэтому сначала обязательно загрузите и установите Node.js, если у вас его нет на вашем компьютере.

Создать каталог

Создайте новый каталог, в котором будет храниться проект, и перейдите в этот каталог:

mkdir recording-tutorial
cd recording-tutorial

Инициализировать проект

Затем инициализируйте проект с помощью npm:

npm init -y

Опция -yсоздается package.jsonсо значениями по умолчанию.

Установите зависимости

Затем мы установим Express для создаваемого сервера и nodemon для перезапуска сервера при любых изменениях:

npm i express nodemon

Создайте экспресс-сервер

Теперь мы можем начать с создания простого сервера. Создайте 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.

Добавить скрипт

Наконец, добавьте startскрипт scriptsв package.json:

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

Запустите веб-сервер

Проверим наш сервер. Выполните следующее, чтобы запустить сервер:

npm start

И сервер должен запускаться с порта 3000. Вы можете попытаться получить к нему доступ localhost:3000, но вы увидите сообщение «Cannot 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.

Добавление стилей

Мы также связываем таблицу стилей 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в свой браузер. Вы увидите кнопку записи.

Если сервер еще не запущен

Кнопка пока ничего не делает. Нам нужно будет привязать событие щелчка, которое запустит запись.

Создайте 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возвращает обещание. Если пользователь разрешает веб-сайту запись, обработчик выполнения обещания получает объект MediaStream, который мы можем использовать для мультимедийного захвата видео- или аудиопотоков пользователя.

Захват мультимедиа и потоки

Чтобы использовать объекты API MediaStream для захвата дорожек мультимедиа, нам необходимо использовать интерфейс 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, Chrome на Android или другие, которые его не поддерживают.

Кроме того, для использования getUserMediaтребуются безопасные веб-сайты, что означает либо страницу, загруженную с использованием HTTPS file://, либо из localhost. Итак, если страница не загружена надежно mediaDevicesи getUserMediaбудет неопределенной.

Начать запись

Если условие ложно (то есть поддерживаются оба mediaDevicesи getUserMedia), мы сначала меняем изображение кнопки записи на stop.png, которое вы можете загрузить из Iconscout или репозитория GitHub и поместить в него public/assets/images.

Затем мы проверяем, является ли mediaRecorder- которое мы определили в начале файла — нулевым или нет.

Если он пуст, это означает, что запись не ведется. Итак, мы получаем экземпляр MediaStream, с помощью которого можно начать запись getUserMedia.

Мы передаем ему объект только с ключом audioи значением true, так как мы просто записываем звук.

Здесь браузер предлагает пользователю разрешить веб-сайту доступ к микрофону. Если пользователь разрешит это, будет выполнен код внутри обработчика выполнения:

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

Здесь мы создаем новый MediaRecorder, назначаем его, mediaRecorderкоторый мы определили в начале файла.

Мы передаем конструктор, от которого получен поток getUserMedia. Затем мы начинаем запись с помощью mediaRecorder.start ().

Наконец, мы привязываем обработчики событий (которые мы скоро создадим) к двум событиям dataavailableи stop.

Мы также добавили catchобработчик на случай, если пользователь не разрешает веб-сайту доступ к микрофону или к любому другому исключению, которое может возникнуть.

Остановить запись

Все это происходит, если mediaRecorderне равно нулю. Если оно равно нулю, это означает, что идет запись, и пользователь ее завершает. Итак, мы используем метод mediaRecorder.stop (), чтобы остановить запись:

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

Обработка событий записи мультимедиа

Наш код до сих пор запускает и останавливает запись, когда пользователь нажимает кнопку записи. Затем мы добавим обработчики событий для dataavailableи stop.

По имеющимся данным

dataavailableСобытие инициируется либо когда полная запись делается, или на основании необязательного параметра timesliceпередается на, mediaRecorder.start()чтобы указать количество миллисекунд, что это событие должно быть вызвано. Передача временного интервала позволяет разрезать запись и получать ее по частям.

Создайте mediaRecorderDataAvailableфункцию, которая будет обрабатывать dataavailableсобытие, просто добавив звуковую дорожку Blob в полученном параметре BlobEvent в chunksмассив, который мы определили в начале файла:

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

Чанк будет массивом звуковых дорожек записи пользователя.

На остановке

Прежде чем мы создадим mediaRecorderStop, который будет обрабатывать событие остановки, давайте сначала добавим контейнер HTML-элемента, который будет содержать записанный звук с помощью кнопок Сохранить и Отменить.

Добавьте следующее public/index.html непосредственно перед закрывающим тегом:

<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элемента:

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 = [];
}

В конце концов, мы сбросить mediaRecorderи chunksих начальные значения для обработки следующих записей. С помощью этого кода наш веб-сайт должен иметь возможность записывать звук, и когда пользователь останавливается, он позволяет им воспроизводить записанный звук.

Последнее, что нам нужно сделать, это сделать ссылку на record.jsin index.html. Добавьте scriptв конце body:

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

Посмотрим сейчас. Зайдите localhost:3000в свой браузер и нажмите кнопку записи. Вам будет предложено разрешить веб-сайту использовать микрофон.

Убедитесь, что вы загружаете веб-сайт либо на localhost

Убедитесь, что вы загружаете веб-сайт либо на localhost, либо на HTTPS-сервер, даже если вы используете поддерживаемый браузер. MediaDevices и getUserMediaнедоступны при других условиях.

Щелкните Разрешить. Изображение микрофона изменится на стоп-изображение. Кроме того, вы должны увидеть значок записи в адресной строке в зависимости от вашего браузера. Это означает, что в данный момент веб-сайт использует микрофон.

Попробуйте записать несколько секунд

Попробуйте записать несколько секунд. Затем нажмите кнопку остановки. Изображение кнопки снова изменится на изображение микрофона, а аудиоплеер отобразится с двумя кнопками — Сохранить и Отменить.

Далее мы реализуем события нажатия кнопок

Далее мы реализуем события нажатия кнопок » Сохранить» и » Отменить». Кнопка » Сохранить» должна загрузить звук на сервер, а кнопка » Отменить» — удалить его.

Отменить обработчик события клика

Сначала мы реализуем обработчик событий для кнопки » Отменить». При нажатии на эту кнопку пользователь должен сначала показать запрос, подтверждающий, что он хочет отказаться от записи. Затем, если пользователь подтвердит, он удалит аудиоплеер и скроет кнопки.

Добавьте переменную, которая будет удерживать кнопку 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);

Теперь вы можете попробовать что-нибудь записать, а затем нажать кнопку » Отменить». Аудиоплеер будет удален, а кнопки скрыты.

Загрузить на сервер

Сохранить обработчик события клика

Теперь мы реализуем обработчик кликов для кнопки » Сохранить». Этот обработчик загрузит на audioBlobсервер, используя Fetch API, когда пользователь нажимает кнопку » Сохранить».

Если вы не знакомы с Fetch 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.listenв 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, что вернет истину, если звук в данный момент не воспроизводится.

Мы также используем значки воспроизведения и паузы, которые будут отображаться внутри каждой записи. Вы можете получить эти значки из Iconscout или репозитория GitHub.

Мы будем использовать 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 позволяет нам добавлять мультимедийные функции для пользователя, такие как запись звука. Веб-API MediaStream также позволяет записывать видео, делать снимки экрана и т.д. Следуя информации, приведенной в этом руководстве, наряду с полезными руководствами, предоставленными MDN и SitePoint, вы также сможете добавить на свой веб-сайт целый ряд других мультимедийных функций.

Читайте также:  Основные тренды разработки внешнего интерфейса, которым следует следовать 2023
Оцените статью
bestprogrammer.ru
Добавить комментарий