Вывод видео с нескольких web-камер на одной странице

db065442997c43b3aa64862662af4177.GIF Как-то раз я приуныл, делать ничего не хотелось, и тут я вспомнил, что в детстве мне сильно хотелось иметь пульт видео наблюдения, как у какого-то злодея из кино, который сидит в темной комнате и хохочет, наблюдая за беспомощными людишками, которые пытаются найти выход. Ну и освежив свои детские воспоминания, я решил воплотить их в жизнь, ну ту часть с пультом наблюдения, без людишек. И тут моим другом стал шагающий семимильными шагами HTML5, а если точнее Stream API.Так как я раньше уже использовал getUserMedia для захвата звука с микрофона, я подумал, что с видео тоже не будет никаких проблем, но они все же вылезли на свет. Т.е. проблем с самим захватом видео-потока не было, а вот с одновременным выводом данных с нескольких источников на одной странице оказалось не все так просто, как хотелось.Итак, начнем с самого начала, а именно с захвата и вывода видео с одного источника. Для этого мы будем использовать ф-ю getUserMedia, которая поддерживается во всех нормальных браузерах старших версий (Stream API), ну разумеется кроме IE.

ПоясненияВсе примеры кода ниже будут писаться на angularjs, ибо сейчас пишу на нем. Все скрипты будут написаны для работы с браузерами Chrome и Opera, ниже будет написано почему. getUserMedia Для доступа к веб-камере необходимо запросить у пользователя разрешение, и тут на сцену выходит getUserMedia, она принимает три аргумента: constraints — тут мы указываем, к какому типу данных мы хотим получить доступ. Его мы рассмотрим ниже более подробно; successCallback — функция возвращает объект LocalMediaStream, это и есть наш поток с камеры; errorCallback — функция отрабатывает, если при попытке захвата потока происходит ошибка или если пользователь отказался предоставить доступ к своему устройству. В качестве средства вывода мы будем использовать элемент video, в атрибут src которого будет передаваться URL элемент в формате Blob из объекта LocalMediaStream.В результате, самая простая ф-я для захвата потока будет выглядеть так: //Кусок из директивы navigator.webkitGetUserMedia ({'video': true}, function (stream) { var video = document.createElement («video»); video.src = window.URL.createObjectURL (stream); video.controls = true; video.play (); angular.element (document.querySelector ('body')).append (video); }, function (e) { alert («Ошибка при доступе к камере!»); }); Тут происходит следующее: Мы создаем элемент video; При помощи ф-и createObjectURL из объекта LocalMediaStream мы создаем URL элемент типа Blob, который передаем в качестве источника в элемент video; Разрешаем авто-воспроизведение; Вставляем наш созданный элемент на страницу. Задача минимум решена, мы вывели поток с одной камеры на свою страничку. Теперь нам надо вывести потоки с остальных наших камер.MediaStreamTrack Конечно же, в попытках решить свою проблему, я обратился за помощью к объекту MediaStreamTrack, который представляет собой интерфейс для работы с потоками со всех мультимедийных устройств, до которых браузер смог добраться. MediaStreamTrack пока довольно-таки редкий зверь и встречается в последних версиях Chrome, Opera и Firefox. Так зачем же он нам нужен? А затем, чтобы получить информацию об источниках данных.В общем, мы нащупали путеводную нить для решения нашей задачи. Только я почувствовал радость от сбывающейся мечты, как я понял, что не могу получить все источники разом для их вывода. После истерического поиска решения было установлено, что в Chrome и Opere объект MediaStreamTrack имеет ф-ю getSources, которая и является нашим спасением. Как видно из названия, эта ф-я возвращает объект, который содержит в себе информацию обо всех источниках аудио и видео.

Ну так найдем наши камеры:

getMediaSources: function () { var mediaSources = []; MediaStreamTrack.getSources (function (sources) { an.forEach (sources, function (val, key) { if (sources[key].kind === 'video') { mediaSources.push (val); } }); }); } Объект sources, который нам предоставила ф-я getSources, представляет из себя массив объектов с информацией об источниках данных. Каждый из этих объектов содержит следующую информацию: id — уникальный идентификатор источника, генерируется браузером; kind — тип, к которому относится источник (audio или video); label — метка устройства (источника), в моём случае там было USB Video Device; facing — как я понял, параметр имеет значение только для мобильных платформ и указывает на переднюю и заднюю камеру (Принимает два значения User — фронт-камера и environment — задняя камера). Решение Таким образом, подведем итог того, что мы теперь умеем. Мы можем получить список всех источников с идентификаторами источников, а так же можем перехватывать данные с них и выводить. Осталось только сложить это все воедино, и мы получим то, к чему стремились.Последовательность действий у нас будет такая:

При загрузке страницы при помощи ф-и MediaStreamTrack.getSources мы определяем все источники видео сигнала; Выводим список источников на страницу. Делаем мы это для того, что нам все-таки придется давать разрешение на доступ к каждой камере. Этого можно избежать в том случае, если страница работает через https При нажатии на какой-либо источник из списка, мы перехватываем данные с него при помощи GetUserMedia, создаем элемент video для него и выводим. (Если выбираем один и тот же источник несколько раз, то просто будет делаться копия потока) Перед тем как привести окончательный рабочий пример, мы вернемся к ф-и webkitGetUserMedia, а именно к её первому аргументу constraints. В документации написано, что туда передаются типы источников в формате: {«video»: true, «audio»: true} Нам этого явно мало, ведь нам надо как минимум передать идентификатор источника. Оказывается, что вместо стандартного объекта можно передать так называемый ограничительный объект. Благодаря ему, мы можем настроить довольно-таки много параметров, таких как частота кадров и разрешение. var constraints = {}; constraints.video = { mandatory: { minWidth: 640, minHeight: 480, minFrameRate: 30 }, optional: [ { sourceId: sourceid } ] }; Наш объект делится на две части: mandatory — тут указываются обязательные ограничения для нашего видео и, если они не могут быть выполнены, то будет вызвано исключение. optional — это не обязательные параметры, которые при возможности будут применены к потоку (т.е. если мы тут укажем, что на выходе мы хотим иметь видео сигнал с частотой кадров не 30, а 60, и наша камера обеспечивает такой поток, то мы получим то, что хотим, а если камера не удовлетворяет условиям, то видео будет выводиться с частотой 30 кадров, что будет соответствовать значению параметра minFrameRate в блоке mandatory). Из параметров, которые можно настраивать я нашел такие: frameRate — частота кадров aspectRatio — соотношение сторон minWidth — минимальная ширина minHeight — минимальная высота sourceId — уникальный идентификатор источника width height Теперь все готово для того, чтобы написать окончательный вариант нашего модуля: Код модуля /** * Created by abaddon on 11.09.14. */ /*global window, document, angular, MediaStreamTrack, console, navigator */ (function (w, d, an, mst, nav) { «use strict»; angular.module («camersRoom», []). value (»$sectors», {}). directive («ngVideoSector», ['$sectors', function ($sectors) { return { restrict: «A», link: function (scope, elem, attr) { $sectors[attr.ngVideoSector] = elem; } }; }]). directive («ngRoomPlace», [»$room»,»$sectors»,»$compile», function ($room, $sectors, $compile) { return { restrict: «A», controller: function ($scope, $element) { this.createViews = function (html) { var videoBlock = $sectors.rec, content; videoBlock.append (html); content = videoBlock.contents (); $compile (content)($scope); }; }, link: function (scope, elem, attr, cont) { if ($room.support) { var mediaSources = [], html, count; $room.getMediaSources ().then (function (sources) { an.forEach (sources, function (val, key) { if (sources[key].kind === 'video') {/*find only video devices. Отбираем только видео устройства*/ mediaSources.push (val); } }); count = mediaSources.length; if (count) { html = $room.createSourcePreview (mediaSources); cont.createViews (html); } else { scope.error = { show: true, text: «Ну для работы надо хоть одну камеру подключить!» }; } /*create video block views.*/ }); } else { scope.error = { show: true, text: «Очень жаль, но ваш браузер никуда не годится. Откройте Google Chrome» }; } } }; }]). factory (»$room», [»$q»,»$sectors», function ($q, $sectors) { var Room = function () { var methods = { get support () { return! this.media; }, set support (value) { this.media = value; } }; an.extend (this, methods); this.support = mst.getSources; }; Room.prototype = { _createVideoElement: function (stream) { var video = d.createElement («video»); video.src = w.URL.createObjectURL (stream); video.controls = true; video.play (); $sectors.place.append (video); }, getMediaSources: function () {/*get all media sources. Получение всех медиа аудио, видео устройств*/ var defer = $q.defer (); mst.getSources (function (sources) { defer.resolve (sources); }); return defer.promise; }, createSourcePreview: function (mediaSources) { var htmlString = '', i = 0; an.forEach (mediaSources, function (val) { i++; htmlString += ''; }); return htmlString; }, addVideoPlace: function (sourceid) { var constraints = {}; constraints.video = { mandatory: { minWidth: 640, minHeight: 480, minFrameRate: 30 }, optional: [ { sourceId: sourceid } ] }; nav.webkitGetUserMedia (constraints, function (stream) { this._createVideoElement (stream); }.bind (this), function (e) { alert («Ошибка при получении потока с камеры!»); }); } }; return new Room (); }]); }(window, document, angular, MediaStreamTrack, navigator)); Приводить код html — шаблона я не буду. Все можно посмотреть на демке и на github.

На этом все, спасибо за внимание, и надеюсь, что эта статья будет кому-нибудь полезна.

Ну и список литературы конечно:

© Habrahabr.ru