Так как я раньше уже использовал 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 += '
Приводить код 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.
Комментариев нет:
Отправить комментарий