Red5: Практика работы с потоковым мультимедиа. Часть 9

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

Все задачи работы с видеопотоками во flash и red5 сводятся, очевидно, к двум направлениям: как захватить изображение с веб-камеры и передать его на сервер, а также как можно загружать видео с сервера и показывать его на страничке сайта. Сегодня я сосредоточусь на описании второй задачи (показ видео), а работу с веб-камерой оставлю на следующую и последнюю статью в серии. Но перед тем как перейти к рассмотрению конкретных приемов, позволяющих загрузить во flash-приложение видеофайл с red5-сервера, я хочу напомнить вам основные подходы, которые существуют для работы с видео во flash. Вкратце: есть три способа, как можно во flash-приложении показать видеоролик. Первый способ предполагает, что видеофайл будет внедрен внутрь самого swf-файла. Грубо говоря, создатель flash-ролика, работая в среде adobe flash cs (хотя подобная функция доступна еще со времен flash mx), может выбрать команду меню “импортировать видеофайл на временную шкалу”. Как результат видеофайл будет разбит на отдельные кадры, и они будут оформлены как ключевые кадры стандартной временной шкалы flash-ролика. К преимуществам такого подхода относится возможность дальнейшей работы с получившейся временной шкалой. Так вы можете добавлять эффекты и анимацию на шкалу с помощью стандартных инструментов adobe flash cs. Однако и недостатков такого подхода много: начиная с ограничений на максимальную продолжительность видеоролика и заканчивая возможными проблемами с синхронизацией изображения и звука. Также вы столкнетесь со сложностями замены видеороликов. Для этого вам придется открывать fla- файл в среде разработки adobe flash cs, удалять старый и повторно импортировать новый видеофайл и, конечно, вам нужно будет повторно запустить и функцию публикации fla-файла в swf-ролик. На все это тратится много времени, а значит, неприемлемо для поставленной мною цели создать видеогалерею, видеоролики в которую будут динамически добавляться и удаляться.

Второй способ работы во flash с видео (этот способ называется progressive download) предполагает, что видеоролик хранится на сайте в виде обычного файла с расширением flv. И на этот файл есть ссылка из стандартного для flash компонента FLVPlayback (в случае flex это компонент VideoDisplay). В любом случае, у этих компонентов есть атрибут “source”, значение которого как раз и равно пути к flv-файлу. Использование progressive загрузки файла предполагает, что flash-клиент последовательно загружает видеофайл от начала и до конца. А по мере того, как сегменты, на которые разделен файл, становятся доступными клиенту, то он может просматривать содержимое ролика. После того как все сегменты ролика были загружены и сохранены во временный файл, вы можете свободно перемещаться по временной шкале видеоролика и смотреть любой из моментов. Чтобы не ходить далеко, я здесь же покажу на flex пример небольшого видеопроигрывателя. Этот пример делается в рамках все того же проекта java + flash, с которым мы работали все прошлые статьи, и требует минимальных усилий. Во-первых, я создал в каталоге веб-модуля папку для хранения видеофайлов “static-video” и скопировал туда flv-файл “aliens.flv”. Теперь мне нужно отредактировать файл Main.mxml и создать в нем заготовку простенького интерфейса для видеоплеера:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx=" сайт layout="absolute">
<mx:VBox>
<mx:HBox>
<mx:Button label="Play" click="video.play()"/>
<mx:Button label="Pause" click="video.pause()"/>
<mx:Button label="Stop" click="video.stop()"/>
</mx:HBox>
<mx:VideoDisplay source="static-video/aliens.flv"
autoPlay="false" id="video" width="320" height="240"/>
</mx:VBox>
</mx:Application>

То, что у меня должно получиться, показано на рис. 1, а пока пару слов о том, что делается в этом примере. Структурно мое flex-приложение состоит из двух расположенных вертикально блоков: набор кнопок для управления видеопроигрывателем и сам проигрыватель (компонент VideoDisplay). Для этого компонента атрибут “source” должен указывать на путь к flv-файлу. Еще мне потребовалось указать имя (video) для компонента VideoDisplay для того, чтобы иметь возможность в последующем вызывать на видеопроигрывателе различные функции. Так, я создал три кнопки “Play”, “Pause”, “Stop”, по нажатию на которые вызываются одноименные методы класса VideoDisplay. Таким образом, наш видеопроигрыватель научился запускать, приостанавливать и прерывать процесс показа видеоролика.

Теперь вернемся немного назад к перечислению возможных способов работы с видео во flash. Третий способ работы с видео называется streaming и является дальнейшим развитием progressive способа доставки видеоинформации. Но сейчас нам уже недостаточно любого http-сервера для хранения, собственно, видеофайла – нам нужен специальный медиасервер (список таких серверов я приводил в самой первой статье серии). Рассматриваемый нами медиасервер red5 позволяет в рамках установленного соединения (NetConnection) открыть поток для передачи видеоинформации (NetStream) и присоединить его к компоненту для просмотра видеопотока. Из очевидных плюсов streaming-а можно назвать возможность работы с динамически формируемым видеопотоком, например, изображение, постоянно поступающее с расположенной на улице веб-камеры. Также есть возможность динамически, на основании качества соединения с клиентом (пропускная ширина канала), определить, какой видеопоток нужно отдать. Еще мы можем, не дожидаясь загрузки всего файла, перейти к любому моменту на временной шкале. При этом с сервера будет загружена лишь нужная нам часть видеоролика. Также не забывайте, что поставка видео (NetStream) является теперь частью всего приложения, то есть таким же ресурсом, как и SharedObject. А значит, в рамках одного приложения мы можем выполнять вызов методов, расположенных на сервере, обмениваться информацией между несколькими подключенными к SharedObject клиентами, а также загружать и показывать видеоинформацию. Построить несложное видеоприложение во flex можно с помощью всего трех классов: NetConnection – для установления соединения с сервером, NetStream – для загрузки видеопотока и Video – графический компонент, умеющий присоединяться к NetStream и показывать приходящее по каналу изображение.

Для следующего примера (работа со streaming) мне потребуется переименовать созданный ранее каталог с видеофайлом: так, ранее он назывался “static-video/aliens.flv”, а теперь должен называться “streams/alien.flv”. Все дело в том, что в случае работы с progressive download нет никаких ограничений на имя каталога с flv-файлом. В случае же со streaming red5-сервер делает предположение, что все видеоролики должны находиться именно внутри подкаталога “/streams”. Впоследствии я покажу, как это правило можно изменить, но сейчас лучше сосредоточимся на создании скелета видеопроигрывателя. К слову, приятной функцией red5 является механизм автоматической блокировки тех видеофайлов, которые сейчас просматриваются каким-то из клиентов (то есть, например, их нельзя удалить из файловой системы, пока клиенты не отсоединятся от файла). Далее я привожу mxml-код примера:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx=" сайт layout="absolute"
creationComplete="completeCreation()">
<mx:Script><![CDATA[
import flash.events.MouseEvent;
private var nc:NetConnection;
private var ns:NetStream;
private var v:Video;

public function completeCreation():void {
nc = new NetConnection();
nc.addEventListener(NetStatusEvent.NET_STATUS, netStatus);
nc.connect('rtmp://localhost/warmodule/');
}
private function netStatus(event:NetStatusEvent):void {
if (event.info.code == 'NetConnection.Connect.Success') {
v = new Video();
placeholder.addChild(v);
ns = new NetStream(nc);
v.attachNetStream(ns);
ns.client = this;
ns.play(“aliens.flv");
} }

public function onMetaData(info:Object):void {
trace("metadata: duration=" + info.duration + " width=" + info.width + " height=" + info.height + " framerate=" + info.framerate); }
public function onCuePoint(info:Object):void {
trace("cuepoint: time=" + info.time + " name=" + info.name + " type=" + info.type);}
]]></mx:Script>
<mx:UIComponent id="placeholder" width="320" height="240"/>
</mx:Application>

Заготовка моего видеоплеера состоит только из одного компонента UIComponent. Напомню, что существует правило, согласно которому все компоненты, из которых “собирается” mxml-приложение, должны быть наследованы от интерфейса IUIComponent. К сожалению, компонент Video, который мы хотим использовать для отображения видеопотока, не является заранее подготовленным для работы совместно с другими mxml-компонентами, и мы не можем поместить Video непосредственно внутрь “mx:Application” или любого другого визуального компонента flex. Приходится помещать на экран сначала компонент-заглушку “mx:UIComponent” и только затем внутрь этого “placeholder”-а можно поместить Video (что и делается внутри функции netStatus). Теперь обратим внимание на функцию completeCreation. Она вызывается автоматически, когда среда выполнения flash завершит начальное конструирование интерфейса (создаст Application и UIComponent). Внутри функции completeCreation я создаю объект подключения к red5-серверу (NetConnection) и подписываю функцию netStatus на получение извещения о том, что соединение было успешно установлено. Когда это происходит, мне остается только создать объект Video и поместить его внутрь “заготовки” placeholder. Последний шаг – это создание специального объекта NetStream, затем привязка его к NetConnection и к Video (функция attachNetStream). Для запуска видеопроигрывателя мне нужно только вызвать метод play, указав как его параметр имя видеофайла.

Остается только раскрыть вопрос о назначении функций onMetaData и onCuePoint. Все дело в том, что созданный мною прототип видеопроигрывателя хотя и работает, но крайне неудобен и делает слишком много необоснованных предположений. Так, выбранный мною размер компонента placeholder в 320*240 пикселей может не совпадать с реальным размером видеофайла. А значит, мне нужно откуда-то получить информацию о разрешении видеоролика, а также о его длительности. Ведь без этой информации я не смогу правильно создать временную шкалу и показывать на ней прогресс показа видеоролика. Все, что я перечислил выше, а также многое другое, например, информация об используемых кодеках, о частоте кадров и т.д. - вся эти сведения называются метаинформация и автоматически загружаются с red5-сервера. Когда метаинформация поступает внутрь NetStream, то он проверяет значение своего свойства client. Если client не было задано, то будет показано подобное сообщение об ошибке: “flash.net.NetStream не удалось осуществить обратный вызов onMetaData”. Поскольку я явно сделал присвоение “ns.client = this”, то NetStream вызовет метод onMetaData на указанном объекте this, то есть на самом приложении. Внутри метода onMetaData я просто печатаю на экран основную информацию о видеопотоке: его длительность, размер и частоту кадров. В практике имеет смысл сразу после получения этих сведений подстроить интерфейс видеопроигрывателя, например, так:

//подгоняем размер компонентов Video и UIComponent под разрешение видеопотока
v.width = info.width;
v.height = info.height;
placeholder.width = info.width;
placeholder.height = info.height;

Что касается функции onCuePoint, то она демонстрирует очень приятную возможность, которая есть во flash-видео: контрольные точки. Идея заключается в том, что можно некоторые моменты видеофильма пометить специальным образом. Например, в эти моменты видеопоток может приостанавливаться, чтобы дать возможность клиенту сделать какой-то выбор, реализуя таким, хоть и грубым образом, идею интерактивного фильма, или можно таким образом помечать начало и конец рекламных вставок. Подобных приемов использования cue points вы сами можете придумать очень много. Для того чтобы внедрить cue points в flv-файл, не требуется ничего сложного. Так, я решил использовать идущий в стандартной поставке adobe flash cs инструмент для конвертирования видеофайлов в flv-формат - Flash Video Encoder. После того как я его запустил и выбрал произвольный avi-файл для конвертации, я нажимаю кнопку Settings и попадаю на диалоговое окно с различными параметрами конвертации: качество, изменение разрешения видео. Среди всех этих настроек есть вкладка Cue Points, где я могу выбирать на временной шкале любые из моментов фильма и присоединять к этому кадру cue point. Каждая cue point имеет свое имя, тип и список произвольных пар “переменная и ее значение” (см. рис. 2). Приятно, что перечень cue points можно сохранять и в последующем загружать из специального xml-файла. Назначенный вами набор cue points будет внедрен внутрь flv-файла в ходе его конвертации. Затем по мере того как видеофайл будет проигрываться, NetStream, как только встретит cue point, вызовет определенный вами метод onCuePoint и передаст туда сведения о встреченной cue point.

По правде сказать, созданная мною заготовка видеопроигрывателя не имеет необходимых для ее практического использования компонентов: кнопок перемотки, возможности управлять громкостью звука, временной шкалы, на которой показывается и общее время просмотра видеоролика, и объем уже загруженных (буферизованных) данных. Сразу скажу, что если перед вами стоит задача быстро внедрить на сайт видеопроигрыватель с перечисленным выше функционалом, то не стоит изобретать велосипеда: в Интернете можно найти множество как бесплатных, так и платных видеопроигрывателей. Если же вы твердо решили делать видеопроигрыватель “с нуля”, то гораздо удобнее использовать для этого не класс Video, а класс VideoDisplay: я уже упоминал о нем, когда показывал пример с progressive download. Однако VideoDisplay умеет работать и со streaming (так как внутри VideoDisplay прозрачно работают и NetConnection, и NetStream, и Video). Итак, я создаю mxml-компонент VideoDisplay:

<mx:VideoDisplay id="videoDisplay" width="320" height="240"/>
А когда мне потребуется показать в нем flv-файл, размещенный в каталоге streams, то я делаю так:
videoDisplay.source = "rtmp://localhost/warmodule/buran.flv";
Один из самых популярных вопросов, связанных с загрузкой видеофайлов, – это возможность указания нестандартного каталога для хранения видеофайлов. В принципе, в средах linux|unix все это можно решить за счет создания “мягкой” ссылки из каталога мое-веб-приложение/streams на любой желаемый вами каталог, но можно обойтись и чисто программным путем. Дело в том, что red5 построен на базе идеологии spring, и многие из компонентов red5 являются spring-бинами. А это значит, что мы можем создавать собственные бины, которые переопределяют стандартную функциональность. Так, за вычисление путей в файловой системе отвечает так называемый streamFilenameGenerator-сервис. Я создал java-класс blz.red5demo.StreamsLocator:

public class StreamsLocator implements org.red5.server.api.stream.IStreamFilenameGenerator {
public String generateFilename(IScope scope, String name, GenerationType type) {
return generateFilename(scope, name, null, type); }

public String generateFilename(IScope scope, String name, String extension, GenerationType type) {
String result = "c:/video/" + name;
if (extension != null && !extension.equals(""))
result += extension;
return result; }

public boolean resolvesToAbsolutePath() { return true; }
}
Как видите, внутри метода generateFilename (его вызывает red5, когда желает получить путь к видеофайлу) я конструирую полный путь к файлу, предполагая, что он расположен внутри каталога “c:/video”. Естественно, что путь к каталогу не должен быть жестко “зашит” внутри кода приложения, а может быть прочитан из конфигурационного файла. Теперь мне осталось только зарегистрировать новый сервис в файле red5-web.xml, добавив туда такую строку:
<bean id="streamFilenameGenerator" class="blz.red5demo.StreamsLocator" />

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

black-zorro@tut.by black-zorro.com


Компьютерная газета. Статья была опубликована в номере 03 за 2010 год в рубрике программирование

©1997-2024 Компьютерная газета