Запись видео от пользователя

Mat Scales

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

Самый простой способ — просто попросить пользователя предоставить заранее записанный файл. Сделайте это, создав простой элемент ввода файла и добавив фильтр accept , который указывает, что мы можем принимать только видеофайлы, и атрибут capture , который указывает, что мы хотим получить его непосредственно с камеры.

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

Этот метод работает на всех платформах. На рабочем столе пользователю будет предложено загрузить файл из файловой системы (игнорируя атрибут capture ). В Safari на iOS откроется приложение камеры, позволяющее записать видео, а затем отправить его обратно на веб-страницу; на Android пользователю предоставляется выбор, какое приложение использовать для записи видео перед отправкой его обратно на веб-страницу.

Многие мобильные устройства имеют более одной камеры. Если у вас есть предпочтения, вы можете установить атрибут capture user , если вы хотите, чтобы камера была обращена к пользователю, или environment , если вы хотите, чтобы камера была обращена наружу.

<input type="file" accept="video/*" capture="user" />
<input type="file" accept="video/*" capture="environment" />

Обратите внимание: это всего лишь подсказка: если браузер не поддерживает эту опцию или тип камеры, который вы запрашиваете, недоступен, браузер может выбрать другую камеру.

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

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

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

Получив доступ к файлу, вы сможете делать с ним все, что захотите. Например, вы можете:

  • Прикрепите его непосредственно к элементу <video> , чтобы его можно было воспроизвести.
  • Загрузите его на устройство пользователя
  • Загрузите его на сервер, прикрепив к XMLHttpRequest
  • Нарисуйте кадры на холсте и примените к ним фильтры.

Хотя использование метода элемента ввода для получения доступа к видеоданным распространено повсеместно, это наименее привлекательный вариант. Мы очень хотим получить доступ к камере и обеспечить удобство работы прямо на странице.

Доступ к камере в интерактивном режиме

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

Получите доступ к камере

Мы можем напрямую получить доступ к камере, используя API в спецификации WebRTC под названием getUserMedia() . getUserMedia() предложит пользователю доступ к подключенным микрофонам и камерам.

В случае успеха API вернет Stream , который будет содержать данные с камеры или микрофона, и затем мы сможем либо прикрепить его к элементу <video> , присоединить к потоку WebRTC, либо сохранить его с помощью API MediaRecorder .

Чтобы получить данные с камеры, мы просто устанавливаем video: true в объекте ограничений, который передается в API getUserMedia()

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

  var handleSuccess = function (stream) {
    player.srcObject = stream;
  };

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

Если вы хотите выбрать конкретную камеру, вы можете сначала перечислить доступные камеры.

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

Затем вы можете передать идентификатор устройства, который хотите использовать, при вызове getUserMedia .

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

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

Доступ к необработанным данным с камеры

Чтобы получить доступ к необработанным видеоданным с камеры, вы можете нарисовать каждый кадр в <canvas> и напрямую манипулировать пикселями.

Для 2D-холста вы можете использовать метод контекста drawImage , чтобы отрисовать текущий кадр элемента <video> на холсте.

context.drawImage(myVideoElement, 0, 0);

Используя холст WebGL, вы можете использовать элемент <video> в качестве источника текстуры.

gl.texImage2D(
  gl.TEXTURE_2D,
  0,
  gl.RGBA,
  gl.RGBA,
  gl.UNSIGNED_BYTE,
  myVideoElement,
);

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

Подробнее об этом вы можете узнать в нашей статье о применении эффектов реального времени к изображениям и видео .

Сохраните данные с камеры

Самый простой способ сохранить данные с камеры — использовать API MediaRecorder .

API MediaRecorder возьмет поток, созданный getUserMedia , а затем постепенно сохранит данные из потока в выбранное вами место назначения.

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

  stopButton.addEventListener('click', function() {
    shouldStop = true;
  })

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

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

      if(shouldStop === true && stopped === false) {
        mediaRecorder.stop();
        stopped = true;
      }
    });

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

    mediaRecorder.start();
  };

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

В нашем случае мы сохраняем данные непосредственно в массив, который позже можем превратить в Blob , который затем можно использовать для сохранения на нашем веб-сервере или непосредственно в хранилище на устройстве пользователя.

Спросите разрешения использовать камеру ответственно

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

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

Используйте API разрешений, чтобы проверить, есть ли у вас уже доступ

API getUserMedia не дает вам информации о том, есть ли у вас уже доступ к камере. Это создает проблему: чтобы обеспечить приятный пользовательский интерфейс, позволяющий пользователю предоставить вам доступ к камере, вам нужно запросить доступ к камере.

В некоторых браузерах эту проблему можно решить с помощью API разрешений. API navigator.permission позволяет вам запрашивать состояние возможности доступа к определенным API без необходимости повторного запроса.

Чтобы узнать, есть ли у вас доступ к камере пользователя, вы можете передать {name: 'camera'} в метод запроса, и он вернет либо:

  • granted — пользователь ранее предоставил вам доступ к камере;
  • prompt — пользователь не предоставил вам доступ и будет запрошен при вызове getUserMedia ;
  • denied — система или пользователь явно заблокировали доступ к камере, и вы не сможете получить к ней доступ.

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

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

Обратная связь