...

понедельник, 15 сентября 2014 г.

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

Как-то раз я приуныл, делать ничего не хотелось, и тут я вспомнил, что в детстве мне сильно хотелось иметь пульт видео наблюдения, как у какого-то злодея из кино, который сидит в темной комнате и хохочет, наблюдая за беспомощными людишками, которые пытаются найти выход. Ну и освежив свои детские воспоминания, я решил воплотить их в жизнь, ну ту часть с пультом наблюдения, без людишек. И тут моим другом стал шагающий семимильными шагами 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("Ошибка при доступе к камере!");
});




Тут происходит следующее:


  1. Мы создаем элемент video;

  2. При помощи ф-и createObjectURL из объекта LocalMediaStream мы создаем URL элемент типа Blob, который передаем в качестве источника в элемент video;

  3. Разрешаем авто-воспроизведение;

  4. Вставляем наш созданный элемент на страницу.




Задача минимум решена, мы вывели поток с одной камеры на свою страничку. Теперь нам надо вывести потоки с остальных наших камер.

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 — задняя камера).




Решение




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

Последовательность действий у нас будет такая:



  1. При загрузке страницы при помощи ф-и MediaStreamTrack.getSources мы определяем все источники видео сигнала;

  2. Выводим список источников на страницу. Делаем мы это для того, что нам все-таки придется давать разрешение на доступ к каждой камере. Этого можно избежать в том случае, если страница работает через https

  3. При нажатии на какой-либо источник из списка, мы перехватываем данные с него при помощи 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 += '





Приводить код html — шаблона я не буду. Все можно посмотреть на демке и на github.


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


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


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий