Афоризм
Если вам долго не звонят родственники или друзья, значит, у них все хорошо.
Михаил Жванецкий
Последние статьи

 • Активности Android
Многоэкранные Android приложения
 • Fragment dynamic
Динамическая загрузка фрагментов в Android
 • Fragment lifecycle
Жизненный цикл Fragment'ов в Android
 • Fragment example
Пример Fragment'ов в Android
 • Data Binding
Описание и пример Data Binding
 • Пример MVVM
Пример использования MVVM в Android
 • Компонент TreeTable
Описание компонента TreeTable для Swing
 • Пример TreeTable
Пример использования TreeTable
 • Хранилища Android
Внутренние и внешние хранилища данных
 • Пример SQLite
Пример использования SQLite в Android
 • WebSocket
Описание и пример реализации WebSocket
 • Визуальные компоненты
Улучшен компонент выбора даты из календаря
 • Анимация jQuery
Описание и примеры анимации элементов DOM
 • APK-файл Android
Создание apk-файла для android устройств, .dex файлы
 • платформа JaBricks
Платформа OSGi-приложения JaBricks
Поддержка проекта

Если Вам сайт понравился и помог, то будем признательны за Ваш «посильный» вклад в его поддержку и развитие
 • Yandex.Деньги
  410013796724260

 • Webmoney
  R335386147728
  Z369087728698

Захват видеокадра с WebRTC

О технологии WebRTC (Web Real Time Communication) представлено очень много информации на бескрайних просторах Интернета. Не обошел эту тему и я на своем сайте. Но переписывать переписанное, а порой и плохо переведенное и/или интерпретированное, не хочется. Поэтому я решил не столько освещать данную тему, сколько реализовывать примеры использования WebRTC с подробными комментариями, плавно переходящими в описание технологий, методов и элементов.

В данной статье рассмотрим пример получения доступа к камере компьютера (мобильного устройства) и захвата с её помощью видеокадров. Исходный код примера, о котором пойдет речь, Вы можете скачать с Github. В исходном примере камера включается при открытии страницы, и отключить ее невозможно. Я внесу небольшие изменения в исходный код примера, чтобы включать и отключать камеру можно было бы по требованию пользователя. Кроме этого, я реально интегрирую модифицированный пример в интерфейс страницы, чтобы Вы могли всё увидеть «наглядно», а не довольствоваться описанием.

Подключение камеры и получение фото с камеры

Ниже представлен интегрированный в контент страницы пример включения камеры и получение (захват) с неё видеокадра. Для включения камеры необходимо нажать соответствующую кнопку. Перед включением камеры открывается диалоговое окно с запросом к пользователю на получение разрешения. Подробное описание примера представлено ниже.

Камера
Фото с камеры
 

Примечание :
1. Чтобы пример функционировал под управлением Apache(2.4) и Tomcat необходимо использовать SSL-протокол, т.е. пример можно демонстрировать только при безопасном подключении.
2. При разработке примера под управлением IDE (Eclipse) SSL-протокол (SSL-сертификат) не требуется.

Описание примера

Описание примера включает html-код, размещаемый на WEB-странице. Секция заголовка <head> включает загрузку двух файлов : файл стилей webcamera.css и скрипты webcamera.js. В секции <body> представлен интерфейс примера, реализованный выше и подробно описанный ниже после листинга.

Здесь следует отметить,

Листинг страницы html

Скрыть листинг html
<head>
   <link href="webrtc-camera.css" type="text/css" 
                              rel="stylesheet" media="all">
   <script src="webrtc-camera.js"></script>
</head>
<body>
   <table style="height:40px">
      <tr><td width="180px">
                <button id="startStream" class="button">
                    Включить камеру
                </button>
          </td>
          <td>
             <button id="stopStream" class="button">
                    Отключить камеру
             </button>
          </td>
      </tr>
   </table>

   <b>Камера</b><br />
   <div class="camera">
       <video id="video">Видео-поток не доступен</video>
       <button id="startbutton">Получить фото</button> 
   </div>
   <b style="clear:left;float:left;margin-top:20px">
       Фото с камеры</b><br />
   <div class="output">
       <!-- элемент размещения видео-кадра с камеры -->
       <img id="photo" alt="Место размещения фото с камеры">
   </div>
   <canvas id="canvas"></canvas>
</body>
 

В верхней части раздела <body> в табличном виде размещаются кнопки включения и отключения камеры. В коде JavaScript (см. ниже) к кнопкам подключаются обработчики событий 'click'. Каждая кнопка имеет свой идентификатор id.

Ниже кнопок размещаются две панели (секции <div>) с классами «camera» и «output», css-стили которых описаны в webcam.css.

Cекция «camera», используемая для захвата видео/аудио потока, включает два элемента : элемент <video> (id="video"), который будет получать видеопоток с камеры, и кнопка <button> с идентификатором startbutton, по нажатию на которую будет осуществляться захват видео-кадра. Обработчик нажатия кнопки подключается после включения камеры.CSS-стиль кнопки (bottom:32px) определяет её нижнее местоположение в области видео, чтобы подтвердить, что выполняется захват видео-кадра, а не скриншот с экрана.

На заметку : элемент <video> включён в проект спецификации HTML-5, используемый для воспроизведения видеозаписей. Видео-элемент может содержать один или несколько источников видео. Чтобы указать источник видео, необходимо использовать атрибут src или элемент <source>; браузер сам определит наиболее подходящий источник. Подробности на Wiki.

Cекция «output» включает элемент img, в котором будет отображаться захваченный видео-кадр.

В интерфейс страницы также включен элемент <canvas>, который является скрытым (в css-стилях обозначен как display:none). Данный элемент используется скриптами (JavaScript) для обработки захваченного видео-кадра и конвертирования его в изображение.

Листинг webcam.css

Содержимое css-файла незначительно отличается от исходного примера. Изменения касаются только включения в классы camera и output атрибутов float:left, чтобы секции размещались у левой границы страницы, и включения описания класса button для кнопок управления камерой.


#video {
  border: 1px solid black;
  box-shadow: 2px 2px 3px black;
  width:320px;
  height:240px;
}

#photo {
  border: 1px solid black;
  box-shadow: 2px 2px 3px black;
  width:320px;
  height:240px;
}

#canvas {
  display:none;
}

.camera {
  display:inline-block;
  float:left;
  width: 340px;
  height: 240px;
}

.output {
  display:inline-block;
  float:left;
  clear:left;
  width: 340px;
  height: 240px;
}

#startbutton {
  display:block;
  position:relative;
  margin-left:auto;
  margin-right:auto;
  bottom:32px;
  background-color: rgba(0, 150, 0, 0.5);
  border: 1px solid rgba(255, 255, 255, 0.7);
  box-shadow: 0px 0px 1px 2px rgba(0, 0, 0, 0.2);
  font-size: 14px;
  font-family: "Lucida Grande", "Arial", sans-serif;
  color: rgba(255, 255, 255, 1.0);
}
 

Описание webcam.js

Ниже представлен листинг скриптового файла webcam.js, в котором обозначена только структура : переменные и наименования методов. Наименования переменных отражают элементы доменной структуры страницы. Каждый из методов будет представлен отдельно. Переменная constraints в формате JSON определяет флаги используемых треков камеры : аудио не будет включено, только видео.

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


(function() {
    var width = 320;  // Ширина видеокадра
    var height = 0;   // Высота видеокадра будет вычислена

    // Флаг streaming показывает включение камеры
    var streaming = false;

    // Элементы HTML страницы, определяемые в методе init()
    var video       = null;
    var canvas      = null;
    var photo       = null;
    var startbutton = null;

    var startStream = null;
    var stopStream  = null;
  
    var constraints = window.constraints = 
                     {audio: false, video: true};

    function init()         {...}
    function clearphoto()   {...}
    function startup()      {...}
    function shutdown()     {...}
    function createphoto(ev){...}
    function takepicture()  {...}

    // Подключение слушателя для вызова метода init после 
    // загрузки страницы и формирования структуры DOM
    window.addEventListener('load', init, false);
})();
 

Листинг метода init

В методе init выполняется инициализация переменных с использованием метода document.getElementById(), т.е. «связываются» переменные с элементами DOM страницы. Кроме этого, к кнопкам управления камерой подключаются слушатели, которые определяют вызываемый при нажатии на кнопку метод. Методом clearphoto формируется изображение на странице.


function init() {
    video       = document.getElementById('video'      );
    canvas      = document.getElementById('canvas'     );
    photo       = document.getElementById('photo'      );
    startbutton = document.getElementById('startbutton');

    startStream = document.getElementById('startStream');
    stopStream  = document.getElementById('stopStream' );

    startStream.addEventListener('click', startup , false);
    stopStream .addEventListener('click', shutdown, false);

    stopStream.disabled = true;

    clearphoto();
}
 

Листинг метода clearphoto

Метод clearphoto получает контекст невидимого canvas, используемый при рендеринге. Для всей канвы устанавливается фоновый цвет (#ddd). После этого, canvas конвертируется в изображение IMG/PNG и методом setAttribute() отображается цветовым фоном в элементе видеокадра (photo).


function clearphoto() {
    var context = canvas.getContext('2d');
    context.fillStyle = "#ddd";
    context.fillRect(0, 0, canvas.width, canvas.height);

    var data = canvas.toDataURL('image/png');
    photo.setAttribute('src', data);
}
 

Листинг метода startup

Прежде чем переходить к описанию старта камеры необходимо рассмотреть объекты MediaDevices и Promice. Интерфейс MediaDevices предоставляет доступ к таким медиа-устройствам, как камера, микрофон, а также к совместному использованию экрана. По сути, он открывает доступ к любому устройству медиа-данных. Интерфейс Promise, представляюший собой «обертку» для объекта, неопределенного на момент создания, позволяет обрабатывать результаты асинхронных операций так, как если бы они были синхронными. Т.е. вместо конечного объекта асинхронного метода возвращается промис (обещание / promice) получить его в ближайшее время.

Объект Promise может находиться в одном из трёх состояний :

  • pending : ожидание (начальное состояние);
  • fulfilled : исполнено (успешное завершение операции);
  • rejected : отклонено (операция завершена с ошибкой).

При создании объекта Promise находится в состоянии ожидания (pending). Затем он может перейти в состояние fulfilled, вернув полученный результат (объект), или быть отклоненным (rejected), вернув причину отказа. В каждом из этих случаев вызывается обработчик, прикрепленный к Promise : методы .then, .catch. (Если в момент вызова обработчика промис уже был исполнен или отклонен, то обработчик все равно будет выполнен). Здесь следует ещё пару слов сказать о прототипе для конструктора Promise, определяемого как Promise.prototype. И последнее : поскольку методы Promise.prototype.then() и Promise.prototype.catch() сами возвращают promise, то их можно связать в «цепочку».

Ну и теперь, после небольшого знакомства с интерфейсами и объектами, необходимыми нам для восприятия порядка включения камеры, можно перейти к коду метода startup, который будет вызван при нажатии кнопки startStream.

Вызовом метода MediaDevices.getUserMedia() с определенными параметрами constraints (видео без аудиопотока) мы получаем promice, который будет обработан либо методом .then при успешном выполнении, либо методом .catch при неуспешном. Каждый из этих методов в качестве параметра получает анонимную функцию с параметром : метод then параметр медиа-потока stream, а метод catch параметр ошибки err. Здесь следует добавить, что при вызове MediaDevices.getUserMedia() у пользователя будет запрошено разрешение на включение камеры.

Таким образом, при успешном включении камеры, промис передает объект медиа-потока stream в качестве параметра анонимной функции метода .then, который присваивается свойству srcObject элемента <video>, направляя в него поток. После этого, вызовом метода HTMLMediaElement.play(), запускаем воспроизведение видеопотока.

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

После листинга метода представлено описание слушателя обработки события начала воспроизведения.


function startup() {
   navigator.mediaDevices.getUserMedia(constraints)
      .then(function(stream) {
         video.srcObject = stream;
         video.play();
      }).catch(function(err) {
         console.log("Ошибка подключения : " + err);
      });
	
   video.addEventListener('canplay', function(ev){
      if (!streaming) {
         height= video.videoHeight/(video.videoWidth/width);
         if (isNaN(height))
            height = width / (4/3);
      
         video .setAttribute('width' , width );
         video .setAttribute('height', height);
         canvas.setAttribute('width' , width );
         canvas.setAttribute('height', height);
         streaming = true;

         tracks = video.srcObject.getTracks();
         console.log('Использется видео-устройство: ' + 
                      tracks[0].label);
         startbutton.addEventListener('click',
                                       createphoto,
                                       false);
	     stopStream.disabled = false;
	     startStream.disabled = true;
      }
  }, false);
}
 

Слушатель события начала воспроизведения

Между командой старта камеры методом HTMLMedрiaElement.play() и началом воспроизведения имеется небольшой промежуток времени, в который устанавливается обработчик события canplay элемента video. В обработчике свойства элемента video конфигурируются на основе формата потока. Флаг streaming допускает только одноразовое выполнение кода. В обработчике корректируется высота видео на основе разницы в размерах между фактической шириной видео (video.videoWidth) и заданной width, на которую будет визуализироваться видео-поток. Методом Element.setAttribute() определяются свойства width и height элементов video и canvas, чтобы соответствовать друг другу.

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

Листинг метода shutdown

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


function shutdown() {
   // проверка работы камеры
   if (streaming) {
      // получаем список треков
      tracks = video.srcObject.getTracks();
      // закрываем каждый трек
      tracks.forEach(function(track) {
         // останов трека
         track.stop();
      });
      // сброс флага работы видекамеры
      streaming = false;
      // удаляем слушатель
      startbutton.removeEventListener('click', 
                                      createphoto,
                                      false);
      // инвертируем доступ к кнопкам
      stopStream .disabled = true;
      startStream.disabled = false;
   }
}
 

Листинг метода createphoto

Захват кадра выполняется методом createphoto, который связан со слушателем нажатия на кнопку startbutton. В методе createphoto вызывается takepicture, после чего вызывается метод Event.preventDefault для предупреждения повторного выполнения действия обработки события более одного раза.


function createphoto(event) {
    takepicture();
    event.preventDefault();
}

 

Листинг метода takepicture

Метод takepicture выполняет последовательно несколько действий : захват текущего видеокадра, конвертацию видеокадра в формат PNG, отображение видеокадра в HTMLImageElement'е photo. Видеокадр формируется в невидимом элементе canvas, размеры которого сначала переопределяются. Методом HTMLCanvasElement.toDataURL() изображение конвертируется в формат PNG. Последним действием вызывается метод photo.setAttribute(), отображая захваченное изображение в элементе изображения (HTMLImageElement).


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();
    }
}

 
  Рейтинг@Mail.ru