Мультимедиа-программирование вместе с Red 5 server. Часть 9

February 8, 2010

Сегодняшняя статья в серии будет посвящена тому, как мультимедиа-сервер 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 и создать в нем заготовку простенького интерфейса для видеоплеера:
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
  3.  <mx:VBox>
  4.   <mx:HBox>
  5.     <mx:Button label="Play" click="video.play()"/>
  6.     <mx:Button label="Pause" click="video.pause()"/>
  7.     <mx:Button label="Stop" click="video.stop()"/>
  8.   </mx:HBox>
  9.   <mx:VideoDisplay source="static-video/aliens.flv"
  10.     autoPlay="false" id="video" width="320" height="240"/>
  11.  </mx:VBox>
  12. </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-код примера:
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
  3. creationComplete="completeCreation()">
  4. <mx:Script><![CDATA[
  5. import flash.events.MouseEvent;
  6. private var nc:NetConnection;
  7. private var ns:NetStream;
  8. private var v:Video;
  9.  
  10. public function completeCreation():void {
  11.     nc = new NetConnection();
  12.     nc.addEventListener(NetStatusEvent.NET_STATUS, netStatus);
  13.     nc.connect('rtmp://localhost/warmodule/');
  14. }
  15. private function netStatus(event:NetStatusEvent):void {
  16.     if (event.info.code == 'NetConnection.Connect.Success') {
  17.       v = new Video();
  18.       placeholder.addChild(v);
  19.       ns = new NetStream(nc);
  20.       v.attachNetStream(ns);
  21.       ns.client = this;
  22.       ns.play(“aliens.flv");
  23.     }  
  24. }
  25.  
  26. public function onMetaData(info:Object):void {
  27.     trace("metadata: duration=" + info.duration + " width=" + info.width + " height=" + info.height +
  28.     " framerate=" + info.framerate); 
  29. }
  30.  
  31. public function onCuePoint(info:Object):void {
  32.   trace("cuepoint: time=" + info.time + " name=" + info.name + " type=" + info.type);
  33. }
  34. ]]></mx:Script>
  35.    <mx:UIComponent id="placeholder" width="320" height="240"/>
  36. </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 я просто печатаю на экран основную информацию об видео-потоке: его длительность, размер и частоту кадров. В практике имеет смысл сразу после получения этих сведений подстроить интерфейс видеопроигрывателя, например, так:
  1. //подгоняем размер компонентов Video и UIComponent под разрешение видео-потока
  2. v.width = info.width;
  3. v.height = info.height;
  4. placeholder.width = info.width;
  5. 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.

По правде сказать, созданная мною заготовка видеопроигрывателя не имеет необходимых для ее практического использования компонентов: кнопок перемотки, возможности управлять громкостью звука, временной шкалы, на которой показывается и общее время просмотра видеоролика, и объем уже загруженных (буферизованных данных). Сразу скажу, что если перед вами стоит задача быстро внедрить на сайт видеопроигрыватель с перечисленным выше функционалом, то не стоит изобретать велосипеда: в internet можно найти множество как бесплатных, так и платных видеопроигрывателей. Если же вы твердо решили делать видеопроигрыватель “с нуля”, то гораздо удобнее использовать для этого не класс Video, класс VideoDisplay: я уже упоминал о нем, когда показывал пример с progressive download. Однако VideoDisplay умеет работать и с streaming (т.к. внутри VideoDisplay прозрачно работают и NetConnection и NetStream и Video). Итак, я создаю mxml-компонент VideoDisplay:
  1. <mx:VideoDisplay id="videoDisplay" width="320" height="240"/>
А когда мне потребуется показать в нем flv-файл, размещенный в каталоге streams, то я делаю так:
  1. videoDisplay.source = "rtmp://localhost/warmodule/buran.flv";
Один из самых популярных вопросов связанных с загрузкой видео-файлов – это возможность указания нестандартного каталога для хранения видео-файлов. В принципе в средах linux|unix все это можно решить за счет создания “мягкой” ссылки из каталога мое-веб-приложение/streams на любой желаемый вами каталог, но можно обойтись и чисто программным путем. Дело в том, что red5 построен на базе идеологии spring и многие из компонентов red5 являются spring-бинами. А это значит, что мы можем создавать собственные бины, которые переопределяют стандартную функциональность. Так за вычисление путей в файловой системе отвечает так называемый streamFilenameGenerator-сервис. Я создал java-класс blz.red5demo.StreamsLocator:
  1. public class StreamsLocator implements org.red5.server.api.stream.IStreamFilenameGenerator {
  2.  
  3.   public String generateFilename(IScope scope, String name, GenerationType type) {
  4.     return generateFilename(scope, name, null, type);   
  5.   }
  6.  
  7.   public String generateFilename(IScope scope, String name, String extension, GenerationType type) {
  8.     String result = "c:/video/" + name;
  9.     if (extension != null && !extension.equals(""))
  10.        result += extension;
  11.     return result;  
  12.   }
  13.  
  14.   public boolean resolvesToAbsolutePath() { 
  15.      return true; 
  16.   }
  17. }
Как видите, внутри метода generateFilename (его вызывает red5, когда желает получить путь к видео-файлу) я конструирую полный путь к файлу, предполагая, что он расположен внутри каталога “c:/video”. Естественно, что путь к каталогу не должен быть жестко “зашит” внутри кода приложения, а может быть прочитан из конфигурационного файла. Теперь мне осталось только зарегистрировать новый сервис в файле red5-web.xml, добавив туда следующую строку:
  1. <bean id="streamFilenameGenerator" class="blz.red5demo.StreamsLocator" />
В следующий раз я завершу рассказ о работе с видеоинформацией и поговорю о том, что мы можем сделать с видео-файлами на стороне java. Например, как можно прочитать список всех видео-файлов в каком-то каталоге и извлечь из них метаинформацию (длительность, размер и т.д.).

Categories: Flash & Flex