Медиатека на сайте
Когда-то вэб-мастера кропели над уменьшением размера страниц и минимизацией объема используемых графических изображений. Теперь, когда инфраструктура сети разрослась так, что высокоскоростные каналы не вызывают трогательного предыхания у технарей, пошла волна тотального использования мультимедии (аудио и видео-роликов). Мне довелось участвовать в проекте, основной целью которого ставилась доработка существующего сайта с целью публикации на нем подкастов (аудио-роликов) и видео.
Прежде чем приступить непосредственно к разработке, пришлось хорошо подумать над основными аспектами публикации информации:
- поскольку мультимедиа будут публиковать в разных форматах и надеяться на то, что пользователи будут заботиться о минимизации размера файла, не приходится, то очевидно, что все ролики перед публикацией должны «нарезаться в формат», т.е. подгоняться под некий внутренний стандарт.
- встраивание роликов как объекты в документ не очень удобно — есть разные платформы и браузеры, которые неизвестно как дружат с медиа-проигрывателями и дружат ли вообще. С другой стороны, есть Flash-плеер, который установлен, наверное, у 98% пользователей и существуют продукты с открытым кодом, позволяющие проигрывать на стороне клиента любые медиа-материалы, дай им только ссылку.
- загрузка файла на сервер — процесс весьма продолжительный и без обратной связи пользователь может его прервать, повторить операцию и вообще — нехорошо.
Потому должна быть возможность отображения статуса загрузки файла. - перекодирование файла — процесс продолжительный. Поэтому делать его сразу после загрузки, мягко говоря, неразумно.
Итак, основные тонкости описаны. Соответственно, было реализовано следующее решение:
Загрузка файла на сервер
Здесь все решилось относительно просто, но не без эксцессов: для нотификации о состоянии загрузки использовался Alternative PHP Cache, подцепляемый как расширение к интерпретатору PHP. Основная функция модуля — кэширование результатов интерпретации PHP-кода (особо отмечу неприятный нюанс — если у вас сайт использует Smarty — эту самую основную функцию следует отключить, используя опцию apc.cache_by_default = Off, в противном случае сможете наблюдать неприятные эффекты работы сайта в целом), но есть еще вспомогательная опция apc.rfc1867, которую надо установить в On. В этом случае (согласно RFC1867 File Upload Progress) APC начнет сообщать вашему сценарию о состоянии загрузки файла на сервер, а именно — сколько всего байт заливается, сколько уже залилось, завершилась ли заливка, где находится временный файл и тому подобное.
Поэтому, после включения APC-модуля в состав PHP, мы можем смело нарисовать такую формочку
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <form method="post" action="/" enctype="multipart/form-data" onsubmit="sendFile(this, '{@id}');"> <div> <input type="hidden" name="usecase" value="UploadFile"/> <input type="hidden" name="APC_UPLOAD_PROGRESS" value="{@id}" /> <input type="file" name="media" /> <br/> <div id="submitPanel" style="padding-top: 10px;"> <input type="submit" value="Upload file..."/> </div> <div id="uploadPanel" class="clearfix"> <div style="float: left; padding: 8px;"> <img src="i/loading.gif"/> </div> <div style="float:left; padding: 8px 10px;"> <div id="sliderover"><div id="sliderin">.</div></div> <div id="sliderout"> <span id="fcurrent">0</span>  из  <span id="ftotal">0</span>  (<span id="fpercent">0</span>%) </div> </div> </div> </div> </form> |
где {id} — уникальный идентификатор закачки (я его формирую на сервере и спускаю в XML в код, но ничего не мешает формировать его на стороне клиента), который APC будет использовать для отслеживания статуса заливки файла, а uploadPanel — строка статуса, которая появляется сразу после начала заливки. Таким образом, процедура закачки файла будет выглядеть как
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | function sendFile(form, id) { $("submitPanel").style.display = 'none'; $("uploadPanel").style.display = 'block'; iframe = new Element('iframe', { name : '_upload_frame', style: 'display: none;', onload: "loadingComplete();" }).hide(); document.body.appendChild(iframe); form.target = iframe.name; uploadMutex = true; uploadStatusUpdate(id); return true; } function uploadStatusUpdate(id) { new Ajax.Request("index.php?usecase=UploadFileStatus&id=" + id, { method: 'get', onSuccess: function(transport) { var json = transport.responseJSON; if(json.current && json.total && json.percent) { $("sliderin").style.width = json.percent + '%'; $("fcurrent").innerHTML = json.current; $("ftotal").innerHTML = json.total; $("fpercent").innerHTML = json.percent; } if(json.percent < 100 && uploadMutex) { setTimeout("uploadStatusUpdate('" + id + "')", 500); } } } ); } |
где функция sendFile создаст невидимый фрейм для хранения результатов загрузки (иначе браузер стремительно будет ожидать получения новой страницы и проигнорирует наши сценарии) и запустит рекурсивную функцию uploadStatusUpdate, которая и будет периодически спрашивать сервер на предмет, сколько там закачалось и долго ли еще ждать, и добросовестно доведет это до сведения пользователя.
Сценарий, запихивающий в JSON-формат результаты выдачи apc_fetch($_GET['id']) — получение статуса закачки — и таблицу стилей оставляю для самостоятельного написания. Или можете поискать статью, на основе которой я это реализовывал, что тоже не сложно.
Перекодирование файла на сервере
Далее, полученный от пользователя файл перебрасывается в каталог для хранения загрузок и делается отметка в БД. Поскольку перекодирование — процесс долгий и может занимать порядочно времени, запуск сценария перекодирования через cron не очень удобно — даже если и учесть блокировку файлов на обработку, особо долгие задания могут привести к тому, что одновременно будет выполнятся несколько преобразований, что весьма сильно отразится на нагрузке на процессор или процессоры. Поэтому в данном случае применилась хорошая идея от разработчиков Киберплата в виде демона (написанного, разумеется, на языке PHP), который периодически опрашивает базу, обнаруживает непреобразованные файлы, помечает их занятыми и запускает процесс перекодирования. В случае положительного результата полученный файл помещается в медиатеку, о чем делается соответствующая отметка в базе данных, оригинал переносится в папку для удаления. В случае отрицательного результата файл помечается в БД как сбойный, а администратору пишется гневное письмо на предмет «шо ж это такое творится, пойди разберись, а?».
Для перекодирования используются внешние программы. Видео преобразуется в flv-формат (по сути дела — такой же контейнер, как и avi), аудио — в mp3. Для преобразования используются ffmpeg и lame соответственно. Flv-формат по завершении требует дополнительной обработки (внедрения метаданных), с чем успешно справляется утилита yamdi.
Демонстрация файлов посетителям
После того, как файлы закачаны и приведены к формату, осталось только всего ничего — показать их посетителям. Сделать это, наверное, проще всего — достаточно выбрать из базы все ссылки с удачно завершенным статусом преобразования и показать их посредством MediaPlayer.
XSL-код этого безобразия у меня выглядит так:
1 2 3 4 5 6 7 8 9 10 11 | <div style="padding: 20px 24xp;"> <div id="container-{id}"><a href="http://www.macromedia.com/go/getflashplayer">Get the Flash Player</a> to see this player.</div> <script type="text/javascript"> var s = new SWFObject("/swf/video.swf","mediaplayer","320","240","8"); s.addParam("allowfullscreen","true"); s.addVariable("width","320"); s.addVariable("height","240"); s.addVariable("file","/<xsl:value-of select="medianame"/>"); s.write("container-<xsl:value-of select="id"/>"); </script> </div> |
Вот так, все достаточно просто. Можете приступать к созданию собственного YouTub-а.


а где этого демона на ПХП от Киберплата можно глянуть? у них на сайте все на перл и си.
Эм… Тут, наверное, записи сбились. Демон сам на Си, а PHP только для него данные подкидывает в базу.