--- title: Захват кадров с WebRTC slug: Web/API/WebRTC_API/Taking_still_photos tags: - Захват WebRTC translation_of: Web/API/WebRTC_API/Taking_still_photos --- <p><span><span><span><span>{{WebRTCSidebar}}</span></span></span></span></p> <p><span class="seoSummary"><span><span><span><span>В этой статье объясняется как использовать WebRTC для получения доступа к камере компьютера или мобильного устройства, и захвата кадров с их помощью. </span></span></span></span></span><a href="https://mdn-samples.mozilla.org/s/webrtc-capturestill"><span><span><span><span>Ознакомьтесь с примером,</span></span></span></span></a><span><span><span><span> а затем узнайте как это работает.</span></span></span></span></p> <p><img alt="Uz WebRTC balstīta attēla uztveršanas lietotne - kreisajā pusē un bez tīmekļa kameras uzņemšanas video straumē un poga" src="https://mdn.mozillademos.org/files/10281/web-rtc-demo.png" style="display: block; height: 252px; margin: 0 auto; width: 677px;"></p> <p><span><span><span><span>Перейдите непосредственно </span></span></span></span><a class="external" href="https://github.com/mdn/samples-server/tree/master/s/webrtc-capturestill" rel="noopener"><span><span><span><span>к коду на Github</span></span></span></span></a><span><span><span><span> , при желании.</span></span></span></span></p> <h2 id="Разметка_HTML"><span><span><span><span>Разметка HTML</span></span></span></span></h2> <p><a class="external" href="https://github.com/mdn/samples-server/tree/master/s/webrtc-capturestill/index.html" rel="noopener"><span><span><span><span>Наш HTML интерфейс</span></span></span></span></a><span><span><span><span> состоит из двух секций : панель отображения видео потока, из которого будет производиться захват и панель отображения результата захвата. Каждая панель имеет свой элемент </span></span></span><span><span><span>{{HTMLElement ("div")}}, для облегчения стилизации и управления.</span></span></span></span></p> <p><span><span><span><span>Первая панель слева содержит два компонента : элемент {{HTMLElement ("video")}} , который будет получать поток, отводимый с камеры, и элемент </span></span></span></span>{{HTMLElement("button")}}, который будет использоваться пользователем для активации захвата видео кадра.</p> <pre> <div class="camera"> <video id="video">Video stream not available.</video> <button id="startbutton">Take photo</button> </div></pre> <p>Все это просто, и мы увидим как они связаны между собой, когда обратимся к коду JavaScript .</p> <p>В разметке имеется элемент {{HTMLElement("canvas")}} , который сохраняет захваченный кадр, который может быть дополнительно обработан и конвертируется в выходной файл изображения. Элемент<code> canvas </code>является скрытым, в его стиле свойство {{cssxref("display")}}<code>:none</code>, во избежании поломки интерфейса, где пользователю совершенно не обязательно видеть служебные элементы.</p> <p>Для отображения пользователю результата захвата кадра, в интерфейсе расположен элемент {{HTMLElement("img")}}.</p> <pre> <canvas id="canvas"> </canvas> <div class="output"> <img id="photo" alt="The screen capture will appear in this box."> </div></pre> <p>Вот и все, что касается HTML. Остальное - просто пух макета страницы и немного текста, предлагающего ссылку на эту страницу.</p> <h2 id="Код_JavaScript">Код JavaScript</h2> <p>Посмотрим на <a href="https://github.com/mdn/samples-server/tree/master/s/webrtc-capturestill/capture.js" rel="noopener">JavaScript code</a>. Разобьём его на части, для упрощения объяснения.</p> <h3 id="Инициализация">Инициализация</h3> <p>Начнём с обёртки всего скрипта в анонимную функцию, во избежании конфликтов глобальных переменных, затем инициализируем различные нужные переменные.</p> <pre>(function() { var width = 320; // Этим создадим ширину фотографии var height = 0; // Это будет вычисляться на основе входящего потока var streaming = false; var video = null; var canvas = null; var photo = null; var startbutton = null;</pre> <p>Все переменные выше:</p> <dl> <dt><code>width</code></dt> <dd>Какой бы не был размер входящего видео, мы намерены масштабировать результирующее изображение к ширине в 320 px.</dd> <dt><code>height</code></dt> <dd>Результирующая высота изображения будет вычисляться на основе переданной ширины и соотношению сторон потока с камеры.</dd> <dt><code>streaming</code></dt> <dd>Указывает на текущую активность видеопотока.</dd> <dt><code>video</code></dt> <dd>Будет содержать ссылку на элемент {{HTMLElement("video")}} после загрузки страницы.</dd> <dt><code>canvas</code></dt> <dd>Содержит ссылку на элемент {{HTMLElement("canvas")}} после загрузки страницы.</dd> <dt><code>photo</code></dt> <dd>Содержит ссылку на элемент {{HTMLElement("img")}} после загрузки страницы.</dd> <dt><code>startbutton</code></dt> <dd>Содержит ссылку на элемент {{HTMLElement("button")}} после загрузки страницы, используется для старта захвата.</dd> </dl> <h3 id="Функция_startup">Функция startup()</h3> <p>Функция <code>startup()</code> запускается, когда страница закончила загрузку, благодаря установке {{domxref("window.addEventListener()")}}. Работа функции состоит в том, что бы запросить доступ у пользователя к его камере, инициализировать элемент {{HTMLElement("img")}} в значение по умолчанию, и установить обработчики событий, необходимых для получения каждого видеокадра с камеры, запускать захват изображения, при нажатии на кнопку.</p> <h4 id="Получаем_ссылки_на_элементы">Получаем ссылки на элементы</h4> <p>Сначала, получим ссылки на основные элементы, доступ к которым нам необходим.</p> <pre> function startup() { video = document.getElementById('video'); canvas = document.getElementById('canvas'); photo = document.getElementById('photo'); startbutton = document.getElementById('startbutton');</pre> <h4 id="Получаем_медиапоток">Получаем медиапоток </h4> <p>Следующая задача - получение медиапотока:</p> <pre> navigator.mediaDevices.getUserMedia({ video: true, audio: false }) .then(function(stream) { video.srcObject = stream; video.play(); }) .catch(function(err) { console.log("An error occurred: " + err); }); </pre> <p>Здесь мы называем метод {{domxref("MediaDevices.getUserMedia()")}} , запрашивая медиапоток без аудиопотока (<code>audio : false</code>). Он возвращает промис, на котором мы определяем методы успешного и не успешного выполнений.</p> <p>Успешное выполнение промиса передаёт объект потока( <code>stream</code> ) в качестве параметра функции метода <code>then()</code>., который присваивается свойству <code>srcObject</code> элемента {{HTMLElement("video")}}, направляя поток в него.</p> <p>Как только поток связан с элементом <code><video></code> , запускаем его воспроизведение, вызовом метода <code><a href="/en-US/docs/Web/API/HTMLMediaElement#play">HTMLMediaElement.play()</a></code>.</p> <p>Метод обработки ошибки промиса вызывается в случае, если получение потока окажется неудачным, к примеру, когда к устройству подключена несовместимая камера, или пользователь запретил к ней доступ.</p> <h4 id="Обработка_события_начала_воспроизведения">Обработка события начала воспроизведения</h4> <p>После момента вызова метода <code><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement#play">HTMLMediaElement.play()</a></code> на элементе {{HTMLElement("video")}}, возникает промежуток времени до начала воспроизведения видеопотока. Для недопущения блокирования интерфейса пользователя в это промежуток, нужно установить обработчик события {{event("canplay")}} элемента <code>video</code> , который сработает, когда элемент начнёт воспроизведение видеопотока. В этот момент все свойства элемента <code>video</code> конфигурируются на основе формата потока.</p> <pre> 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);</pre> <p>Функциональность обработчика не будет запущена, если он запускается повторно. Это отслеживает переменная <code>streaming</code> , которая содержит значение <code>false</code> при первом запуске обработчика.</p> <p>Если это действительно первый запуск, мы устанавливаем высоту видео на основе разницы в размере между фактическим размером видео - <code>video.videoWidth</code> и шириной, на которую мы собираемся его визуализировать - <code>width</code></p> <p>Наконец, свойства <code>width</code> и <code>height</code> элементов<code> video</code> и <code>canvas</code> устанавливаются так, чтобы соответствовать друг другу, вызывая метод {{domxref("Element.setAttribute()")}} на каждом из двух свойств каждого элемента, и, при необходимости, устанавливая ширину и высоту. Наконец, мы установили для переменной <code>streaming</code> значение <code>true</code>, чтобы предотвратить случайное повторное выполнение этого установочного кода.</p> <h4 id="Обработка_нажатий_кнопки">Обработка нажатий кнопки</h4> <p>Для захвата кадра, пользователь каждый раз нажимает кнопку <code>startbutton</code>, нужно добавить обработчик события кнопки, для его вызова при возникновении события {{event("click")}} :</p> <pre> startbutton.addEventListener('click', function(ev){ takepicture(); ev.preventDefault(); }, false);</pre> <p>Метод прост, он вызывает функцию <code>takepicture()</code>, определяемую ниже в секции {{anch("Capturing a frame from the stream")}}, затем вызывает метод {{domxref("Event.preventDefault()")}} на полученном объекте события, для предотвращения действия обработки события более одного раза.</p> <h4 id="Завершение_метода_startup">Завершение метода startup() </h4> <p>Ещё пара строк кода в методе <code>startup()</code>:</p> <pre> clearphoto(); }</pre> <p>Вызов метода <code>clearphoto()</code> описывается в секции {{anch("Clearing the photo box")}}.</p> <h3 id="Отчистка_бокса_для_фотографии">Отчистка бокса для фотографии</h3> <p>Очистка бокса фотографии включает создание изображения, а затем преобразование его в формат, используемый элементом {{HTMLElement ("img")}}, который отображает последний снятый кадр. Код ниже:</p> <pre> 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); }</pre> <p>Начнём с получения ссылки на скрытый элемент {{HTMLElement ("canvas")}}, который мы используем для рендеринга за пределами экрана. Затем мы устанавливаем свойство <code>fillStyle</code> в <code>#AAA</code> ( светло-серый) и заполняем весь холст этим цветом, вызывая метод {{domxref("CanvasRenderingContext2D.fillRect()","fillRect()")}}.</p> <p>Наконец, в этой функции мы конвертируем <code>canvas</code> в изображение PNG и вызываем метод <code>{{domxref("Element.setAttribute", "photo.setAttribute()")}}</code> отображая захваченный цветовой фон в элементе изображения (бокса для фотографии).</p> <h3 id="Захват_кадра_из_видеопотока">Захват кадра из видеопотока</h3> <p>Последняя функция, требующая определения и являющаяся основным смыслом всего примера - <code>takepicture()</code> , которая захватывает текущий видеокадр, конвертирует его в формат PNG, и отображает его в блоке отображения кадра. Код её ниже:</p> <pre> 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(); } }</pre> <p>Как и в случае, когда нам нужно работать с содержимым <code>canvas</code>, мы начинаем с {{domxref("CanvasRenderingContext2D","2D drawing context")}} для скрытого <code>canvas</code>.</p> <p>Затем, если ширина и высота не равны нулю (имеется в виду, что есть, по крайней мере, потенциально допустимые данные изображения), мы устанавливаем ширину и высоту <code>canvas</code>, чтобы они соответствовали ширине захваченного кадра, затем вызываем метод {{domxref("CanvasRenderingContext2D.drawImage()", "drawImage()")}} , что бы отрисовать текущий кадр видео в контексте <code>canvas</code>, заполнив весь холст изображением кадра.</p> <div class="blockIndicator note"> <p><strong>Примечание :</strong> Используется факт того, что интерфейс {{domxref("HTMLVideoElement")}} похож на интерфейс {{domxref("HTMLImageElement")}} для любых API , которые принимают <code>HTMLImageElement</code> в качестве параметра, с текущим кадром видео, представленным как содержимое изображения.</p> </div> <p>Как только <code>canvas</code> будет содержать захваченное видео, конвертируем его в PNG формат, вызывая метод {{domxref("HTMLCanvasElement.toDataURL()")}} на нем; наконец вызываем метод {{domxref("Element.setAttribute", "photo.setAttribute()")}} отображая захваченное изображение в элементе изображения (бокса фотографии).</p> <p>Если подходящее изображение не доступно (то есть, <code>width</code> и <code>height</code> равны 0), отчищаем содержимое элемента изображения, вызывая метод <code>clearphoto()</code>.</p> <h2 id="Приколы_с_фильтрами">Приколы с фильтрами</h2> <p>Поскольку мы снимаем изображения с веб-камеры пользователя, захватывая кадры из элемента {{HTMLElement("video")}} , можно легко применить фильтры и забавные эффекты к элементу <font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);">video</span></font>. Оказывается, любые CSS-фильтры, которые вы применяете к элементу с помощью свойства {{cssxref ("filter")}}, влияют на захваченную фотографию.Эти фильтры могут варьироваться от простых (делая изображение черно-белым) до экстремальных (размытие по Гауссу и вращение оттенка).</p> <p>Вы можете экспериментировать с этими эффектами, используя, например, инструмент разработчика FirefoxYou <a href="/en-US/docs/Tools/Style_Editor">редактор стилей</a>; смотрим <a href="/en-US/docs/Tools/Page_Inspector/How_to/Edit_CSS_filters">Редактирование с CSS фильтрами</a> о подробностях выполнения.</p> <h2 id="Использование_определённых_устройств">Использование определённых устройств</h2> <p>При необходимости вы можете ограничить набор разрешённых источников видео, определённым устройством или набором устройств. Для этого нужно вызвать метод {{domxref("navigator.mediaDevices.enumerateDevices()")}}. Когда промис разрешиться массивом объектов {{domxref("MediaDeviceInfo")}} , описывающих доступные устройства , выберите те, которым хотите разрешить доступ и укажите соответствующий идентификатор устройства {{domxref("MediaTrackConstraints.deviceId", "deviceId")}} или несколько <code>deviceId</code> в объекте {{domxref("MediaTrackConstraints")}} , переданном в {{domxref("MediaDevices.getUserMedia", "getUserMedia()")}}.</p> <h2 id="Смотрите_также">Смотрите также</h2> <ul> <li><a href="https://mdn-samples.mozilla.org/s/webrtc-capturestill">Пробуем пример</a></li> <li><a href="https://github.com/mdn/samples-server/tree/master/s/webrtc-capturestill">Примеры на Github</a></li> <li>{{domxref("Navigator.mediaDevices.getUserMedia()")}}</li> <li>{{SectionOnPage("/en-US/docs/Web/API/Canvas_API/Tutorial/Using_images","Использование изображений")}}</li> <li>{{domxref("CanvasRenderingContext2D.drawImage()")}}</li> </ul>