Захват аудио и видео в HTML5

Захват аудио/видео долгое время был «Святым Граалем » веб-разработки. В течение многих лет нам приходилось полагаться на плагины браузера ( Flash или Silverlight ), чтобы выполнить свою работу. Ну давай же!

HTML5 в помощь. Это может быть неочевидно, но появление HTML5 привело к резкому увеличению доступа к аппаратному обеспечению устройств. Геолокация (GPS), API ориентации (акселерометр), WebGL (графический процессор) и API веб-аудио (аудиооборудование) являются прекрасными примерами. Эти функции невероятно мощны и предоставляют API-интерфейсы JavaScript высокого уровня, которые находятся поверх базовых аппаратных возможностей системы.

В этом руководстве представлен новый API GetUserMedia , который позволяет веб-приложениям получать доступ к камере и микрофону пользователя.

Путь к getUserMedia()

Если вы не знакомы с его историей, то то, как мы пришли к API getUserMedia() представляет собой интересную историю.

За последние несколько лет появилось несколько вариантов «API захвата мультимедиа». Многие люди осознали необходимость иметь доступ к собственным устройствам в Интернете, но это побудило всех и их мам собрать новую спецификацию. Дела пошли настолько запутанно, что W3C наконец решил сформировать рабочую группу. Их единственная цель? Осмыслите безумие! Рабочей группе по политике API устройств (DAP) было поручено консолидировать и стандартизировать множество предложений.

Попробую подвести итог тому, что произошло в 2011 году…

Этап 1: захват HTML-медиа

HTML Media Capture был первой попыткой DAP стандартизировать захват мультимедиа в Интернете. Он работает путем перегрузки <input type="file"> и добавления новых значений для параметра accept .

Если вы хотите, чтобы пользователи могли делать снимки себя с помощью веб-камеры, это возможно с помощью capture=camera :

<input type="file" accept="image/*;capture=camera">

Запись видео или аудио аналогична:

<input type="file" accept="video/*;capture=camcorder">
<input type="file" accept="audio/*;capture=microphone">

Довольно приятно, правда? Мне особенно нравится, что он повторно использует ввод файла. Семантически это имеет большой смысл. В чем этот конкретный «API» не дотягивает, так это в способности создавать эффекты в реальном времени (например, отображать данные веб-камеры в реальном времени в <canvas> и применять фильтры WebGL). HTML Media Capture позволяет только записывать медиафайл или делать моментальный снимок.

Поддерживать:

  • Браузер Android 3.0 — одна из первых реализаций. Посмотрите это видео , чтобы увидеть его в действии.
  • Chrome для Android (0.16)
  • Firefox для мобильных устройств 10.0
  • iOS6 Safari и Chrome (частичная поддержка)

Раунд 2: элемент устройства

Многие считали, что HTML Media Capture слишком ограничен, поэтому появилась новая спецификация, поддерживающая любые типы устройств (будущего). Неудивительно, что проект потребовал создания нового элемента <device> , который стал предшественником getUserMedia() .

Opera была одним из первых браузеров, создавших первоначальную реализацию захвата видео на основе элемента <device> . Вскоре после этого (точнее , в тот же день ) WhatWG решила отказаться от тега <device> в пользу другого новинки, на этот раз JavaScript API под названием navigator.getUserMedia() . Неделю спустя Opera выпустила новые сборки, включавшие поддержку обновленной спецификации getUserMedia() . Позже в том же году к этой вечеринке присоединилась Microsoft, выпустив Lab для IE9, поддерживающую новую спецификацию.

Вот как могло бы выглядеть <device> :

<device type="media" onchange="update(this.data)"></device>
<video autoplay></video>
<script>
  function update(stream) {
    document.querySelector('video').src = stream.url;
  }
</script>

Поддерживать:

К сожалению, ни один выпущенный браузер никогда не включал <device> . Думаю, на один API меньше, о чем стоит беспокоиться :) Однако <device> имел две замечательные особенности: 1.) он был семантическим и 2.) его можно было легко расширить для поддержки не только аудио/видео устройств.

Сделайте вдох. Эта штука движется быстро!

Раунд 3: WebRTC

Элемент <device> в конечном итоге пошел по пути Додо.

Темпы поиска подходящего API захвата ускорились благодаря более масштабным усилиям по WebRTC (Web Real Time Communications). Эта спецификация контролируется рабочей группой W3C WebRTC . У Google, Opera, Mozilla и некоторых других есть реализации.

getUserMedia() связан с WebRTC, поскольку является шлюзом к этому набору API. Он предоставляет средства доступа к потоку локальной камеры/микрофона пользователя.

Поддерживать:

getUserMedia() поддерживается начиная с Chrome 21, Opera 18 и Firefox 17.

Начиная

С помощью navigator.mediaDevices.getUserMedia() мы наконец можем подключиться к входу веб-камеры и микрофона без плагина. Доступ к камере теперь осуществляется одним звонком, а не установкой. Он встроен прямо в браузер. Уже взволнован?

Обнаружение функций

Обнаружение функций — это простая проверка существования navigator.mediaDevices.getUserMedia :

if (navigator.mediaDevices?.getUserMedia) {
  // Good to go!
} else {
  alert("navigator.mediaDevices.getUserMedia() is not supported");
}

Получение доступа к устройству ввода

Чтобы использовать веб-камеру или микрофон, нам необходимо запросить разрешение. Первый параметр navigator.mediaDevices.getUserMedia() — это объект, определяющий детали и требования для каждого типа носителя, к которому вы хотите получить доступ. Например, если вы хотите получить доступ к веб-камере, первым параметром должно быть {video: true} . Чтобы использовать и микрофон, и камеру, передайте {video: true, audio: true} :

<video autoplay></video>

<script>
  navigator.mediaDevices
    .getUserMedia({ video: true, audio: true })
    .then((localMediaStream) => {
      const video = document.querySelector("video");
      video.srcObject = localMediaStream;
    })
    .catch((error) => {
      console.log("Rejected!", error);
    });
</script>

ХОРОШО. Так что же здесь происходит? Захват мультимедиа — прекрасный пример совместной работы новых API HTML5. Он работает вместе с другими нашими помощниками по HTML5, <audio> и <video> . Обратите внимание, что мы не устанавливаем атрибут src и не включаем элементы <source> в элемент <video> . Вместо того, чтобы передавать видео URL-адрес медиафайла, мы устанавливаем srcObject в объект LocalMediaStream , представляющий веб-камеру.

Я также приказываю <video> autoplay , иначе он будет завис на первом кадре. Добавление controls также работает так, как вы и ожидали.

Установка ограничений мультимедиа (разрешение, высота, ширина)

Первый параметр getUserMedia() также можно использовать для указания дополнительных требований (или ограничений) к возвращаемому медиапотоку. Например, вместо того, чтобы просто указывать, что вам нужен базовый доступ к видео (например, {video: true} ), вы можете дополнительно потребовать, чтобы поток был в формате HD:

const hdConstraints = {
  video: { width: { exact:  1280} , height: { exact: 720 } },
};

const stream = await navigator.mediaDevices.getUserMedia(hdConstraints);
const vgaConstraints = {
  video: { width: { exact:  640} , height: { exact: 360 } },
};

const stream = await navigator.mediaDevices.getUserMedia(hdConstraints);

Дополнительные конфигурации см. в разделе API ограничений .

Выбор источника мультимедиа

Метод enumerateDevices() интерфейса MediaDevices запрашивает список доступных устройств ввода и вывода мультимедиа, таких как микрофоны, камеры, гарнитуры и т. д. Возвращенное обещание разрешается с помощью массива объектов MediaDeviceInfo описывающих устройства.

В этом примере в качестве источника медиапотока выбраны последние найденные микрофон и камера:

if (!navigator.mediaDevices?.enumerateDevices) {
  console.log("enumerateDevices() not supported.");
} else {
  // List cameras and microphones.
  navigator.mediaDevices
    .enumerateDevices()
    .then((devices) => {
      let audioSource = null;
      let videoSource = null;

      devices.forEach((device) => {
        if (device.kind === "audioinput") {
          audioSource = device.deviceId;
        } else if (device.kind === "videoinput") {
          videoSource = device.deviceId;
        }
      });
      sourceSelected(audioSource, videoSource);
    })
    .catch((err) => {
      console.error(`${err.name}: ${err.message}`);
    });
}

async function sourceSelected(audioSource, videoSource) {
  const constraints = {
    audio: { deviceId: audioSource },
    video: { deviceId: videoSource },
  };
  const stream = await navigator.mediaDevices.getUserMedia(constraints);
}

Посмотрите великолепную демонстрацию Сэма Даттона о том, как предоставить пользователям возможность выбирать источник мультимедиа.

Безопасность

Браузеры отображают диалоговое окно разрешений при вызове navigator.mediaDevices.getUserMedia() , которое дает пользователям возможность предоставить или запретить доступ к их камере/микрофону. Например, вот диалоговое окно разрешений Chrome:

Диалог разрешений в Chrome
Диалог разрешений в Chrome

Обеспечение резервного варианта

Для пользователей, у которых нет поддержки navigator.mediaDevices.getUserMedia() , один из вариантов — вернуться к существующему видеофайлу, если API не поддерживается и/или вызов по какой-либо причине завершается неудачей:

if (!navigator.mediaDevices?.getUserMedia) {
  video.src = "fallbackvideo.webm";
} else {
  const stream = await navigator.mediaDevices.getUserMedia({ video: true });
  video.srcObject = stream;
}