04
Июн

Медиатека на сайте

Написал Максим Крентовский в Деятельность, Исследования

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

Прежде чем приступить непосредственно к разработке, пришлось хорошо подумать над основными аспектами публикации информации:

  • поскольку мультимедиа будут публиковать в разных форматах и надеяться на то, что пользователи будут заботиться о минимизации размера файла, не приходится, то очевидно, что все ролики перед публикацией должны «нарезаться в формат», т.е. подгоняться под некий внутренний стандарт.
  • встраивание роликов как объекты в документ не очень удобно — есть разные платформы и браузеры, которые неизвестно как дружат с медиа-проигрывателями и дружат ли вообще. С другой стороны, есть 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>
                        &#0160;из&#0160;
                        <span id="ftotal">0</span>
                        &#0160;(<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-а. :)

2 ком.
  1. Владимир:

    а где этого демона на ПХП от Киберплата можно глянуть? у них на сайте все на перл и си.

    google.com mkrentovskiy

    Эм… Тут, наверное, записи сбились. Демон сам на Си, а PHP только для него данные подкидывает в базу.

Максим Крентовский
системный архитектор
E-mail / GTalk: mkrentovskiy@gmail.com
Skype: mkrentovskiy