Захват кадров с помощью getUserMedia()
В этой статье объясняется как использовать navigator.mediaDevices.getUserMedia()
для получения доступа к камере компьютера или мобильного устройства с поддержкой getUserMedia()
и создания изображений.
Разметка HTML
Наш HTML интерфейс состоит из двух секций : панель отображения видео потока, из которого будет производиться захват и панель отображения результата захвата. Каждая панель имеет свой элемент <div>
, для облегчения стилизации и управления.
Первая панель слева содержит два компонента : элемент <video>
, который будет получать поток, отводимый с камеры, и элемент <button>
, который будет использоваться пользователем для активации захвата видео кадра.
<div class="camera"> <video id="video">Video stream not available.</video> <button id="startbutton">Take photo</button> </div>
Все это просто, и мы увидим как они связаны между собой, когда обратимся к коду JavaScript .
В разметке имеется элемент <canvas>
, который сохраняет захваченный кадр, который может быть дополнительно обработан и конвертируется в выходной файл изображения. Элементcanvas
является скрытым, в его стиле свойство display
:none
, во избежании поломки интерфейса, где пользователю совершенно не обязательно видеть служебные элементы.
Для отображения пользователю результата захвата кадра, в интерфейсе расположен элемент <img>
.
<canvas id="canvas"> </canvas> <div class="output"> <img id="photo" alt="The screen capture will appear in this box."> </div>
Вот и все, что касается HTML. Остальное - просто пух макета страницы и немного текста, предлагающего ссылку на эту страницу.
Код JavaScript
Посмотрим на JavaScript code. Разобьём его на части, для упрощения объяснения.
Инициализация
Начнём с обёртки всего скрипта в анонимную функцию, во избежании конфликтов глобальных переменных, затем инициализируем различные нужные переменные.
(function() { var width = 320; // Этим создадим ширину фотографии var height = 0; // Это будет вычисляться на основе входящего потока var streaming = false; var video = null; var canvas = null; var photo = null; var startbutton = null;
Все переменные выше:
width
-
Какой бы не был размер входящего видео, мы намерены масштабировать результирующее изображение к ширине в 320 px.
height
-
Результирующая высота изображения будет вычисляться на основе переданной ширины и соотношению сторон потока с камеры.
streaming
-
Указывает на текущую активность видеопотока.
video
-
Будет содержать ссылку на элемент
<video>
после загрузки страницы. canvas
-
Содержит ссылку на элемент
<canvas>
после загрузки страницы. photo
-
Содержит ссылку на элемент
<img>
после загрузки страницы. -
Содержит ссылку на элемент
<button>
после загрузки страницы, используется для старта захвата.
Функция startup()
Функция startup()
запускается, когда страница закончила загрузку, благодаря установке window.addEventListener()
. Работа функции состоит в том, что бы запросить доступ у пользователя к его камере, инициализировать элемент <img>
в значение по умолчанию, и установить обработчики событий, необходимых для получения каждого видеокадра с камеры, запускать захват изображения, при нажатии на кнопку.
Получаем ссылки на элементы
Сначала, получим ссылки на основные элементы, доступ к которым нам необходим.
function startup() { video = document.getElementById('video'); canvas = document.getElementById('canvas'); photo = document.getElementById('photo'); startbutton = document.getElementById('startbutton');
Получаем медиапоток
Следующая задача - получение медиапотока:
navigator.mediaDevices.getUserMedia({ video: true, audio: false }) .then(function(stream) { video.srcObject = stream; video.play(); }) .catch(function(err) { console.log("An error occurred: " + err); });
Здесь мы называем метод MediaDevices.getUserMedia()
, запрашивая медиапоток без аудиопотока (audio : false
). Он возвращает промис, на котором мы определяем методы успешного и не успешного выполнений.
Успешное выполнение промиса передаёт объект потока( stream
) в качестве параметра функции метода then()
., который присваивается свойству srcObject
элемента <video>
, направляя поток в него.
Как только поток связан с элементом <video>
, запускаем его воспроизведение, вызовом метода HTMLMediaElement.play()
.
Метод обработки ошибки промиса вызывается в случае, если получение потока окажется неудачным, к примеру, когда к устройству подключена несовместимая камера, или пользователь запретил к ней доступ.
Обработка события начала воспроизведения
После момента вызова метода HTMLMediaElement.play()
на элементе <video>
, возникает промежуток времени до начала воспроизведения видеопотока. Для недопущения блокирования интерфейса пользователя в это промежуток, нужно установить обработчик события элемента video
, который сработает, когда элемент начнёт воспроизведение видеопотока. В этот момент все свойства элемента video
конфигурируются на основе формата потока.
video.addEventListener('canplay', function(ev){ if (!streaming) { height = video.videoHeight / (video.videoWidth/width); video.setAttribute('width', width); video.setAttribute('height', height); canvas.setAttribute('width', width); canvas.setAttribute('height', height); streaming = true; } }, false);
Функциональность обработчика не будет запущена, если он запускается повторно. Это отслеживает переменная streaming
, которая содержит значение false
при первом запуске обработчика.
Если это действительно первый запуск, мы устанавливаем высоту видео на основе разницы в размере между фактическим размером видео - video.videoWidth
и шириной, на которую мы собираемся его визуализировать - width
Наконец, свойства width
и height
элементов video
и canvas
устанавливаются так, чтобы соответствовать друг другу, вызывая метод Element.setAttribute()
на каждом из двух свойств каждого элемента, и, при необходимости, устанавливая ширину и высоту. Наконец, мы установили для переменной streaming
значение true
, чтобы предотвратить случайное повторное выполнение этого установочного кода.
Обработка нажатий кнопки
Для захвата кадра, пользователь каждый раз нажимает кнопку startbutton
, нужно добавить обработчик события кнопки, для его вызова при возникновении события :
startbutton.addEventListener('click', function(ev){ takepicture(); ev.preventDefault(); }, false);
Метод прост, он вызывает функцию takepicture()
, определяемую ниже в секции Capturing a frame from the stream, затем вызывает метод Event.preventDefault()
на полученном объекте события, для предотвращения действия обработки события более одного раза.
Завершение метода startup()
Ещё пара строк кода в методе startup()
:
clearphoto(); }
Вызов метода clearphoto()
описывается в секции Clearing the photo box.
Отчистка бокса для фотографии
Очистка бокса фотографии включает создание изображения, а затем преобразование его в формат, используемый элементом <img>
, который отображает последний снятый кадр. Код ниже:
function clearphoto() { var context = canvas.getContext('2d'); context.fillStyle = "#AAA"; context.fillRect(0, 0, canvas.width, canvas.height); var data = canvas.toDataURL('image/png'); photo.setAttribute('src', data); }
Начнём с получения ссылки на скрытый элемент <canvas>
, который мы используем для рендеринга за пределами экрана. Затем мы устанавливаем свойство fillStyle
в #AAA
( светло-серый) и заполняем весь холст этим цветом, вызывая метод fillRect()
.
Наконец, в этой функции мы конвертируем canvas
в изображение PNG и вызываем метод
отображая захваченный цветовой фон в элементе изображения (бокса для фотографии).photo.setAttribute()
Захват кадра из видеопотока
Последняя функция, требующая определения и являющаяся основным смыслом всего примера - takepicture()
, которая захватывает текущий видеокадр, конвертирует его в формат PNG, и отображает его в блоке отображения кадра. Код её ниже:
function takepicture() { var context = canvas.getContext('2d'); if (width && height) { canvas.width = width; canvas.height = height; context.drawImage(video, 0, 0, width, height); var data = canvas.toDataURL('image/png'); photo.setAttribute('src', data); } else { clearphoto(); } }
Как и в случае, когда нам нужно работать с содержимым canvas
, мы начинаем с 2D drawing context
для скрытого canvas
.
Затем, если ширина и высота не равны нулю (имеется в виду, что есть, по крайней мере, потенциально допустимые данные изображения), мы устанавливаем ширину и высоту canvas
, чтобы они соответствовали ширине захваченного кадра, затем вызываем метод drawImage()
, что бы отрисовать текущий кадр видео в контексте canvas
, заполнив весь холст изображением кадра.
Примечание:
Используется факт того, что интерфейс HTMLVideoElement
похож на интерфейс HTMLImageElement
для любых API , которые принимают HTMLImageElement
в качестве параметра, с текущим кадром видео, представленным как содержимое изображения.
Как только canvas
будет содержать захваченное видео, конвертируем его в PNG формат, вызывая метод HTMLCanvasElement.toDataURL()
на нем; наконец вызываем метод photo.setAttribute()
отображая захваченное изображение в элементе изображения (бокса фотографии).
Если подходящее изображение не доступно (то есть, width
и height
равны 0), отчищаем содержимое элемента изображения, вызывая метод clearphoto()
.
Приколы с фильтрами
Поскольку мы снимаем изображения с веб-камеры пользователя, захватывая кадры из элемента <video>
, можно легко применить фильтры и забавные эффекты к элементу video. Оказывается, любые CSS-фильтры, которые вы применяете к элементу с помощью свойства filter
, влияют на захваченную фотографию.Эти фильтры могут варьироваться от простых (делая изображение черно-белым) до экстремальных (размытие по Гауссу и вращение оттенка).
Вы можете экспериментировать с этими эффектами, используя, например, инструмент разработчика FirefoxYou редактор стилей; смотрим Редактирование с CSS фильтрами о подробностях выполнения.
Использование определённых устройств
При необходимости вы можете ограничить набор разрешённых источников видео, определённым устройством или набором устройств. Для этого нужно вызвать метод navigator.mediaDevices.enumerateDevices()
. Когда промис разрешиться массивом объектов MediaDeviceInfo
, описывающих доступные устройства , выберите те, которым хотите разрешить доступ и укажите соответствующий идентификатор устройства deviceId
или несколько deviceId
в объекте MediaTrackConstraints
, переданном в getUserMedia()
.
Смотрите также
- Пример на Github
MediaDevices.getUserMedia
- Использование кадров из видео в руководстве по Canvas
CanvasRenderingContext2D.drawImage()