Простая реализация раздела «сейчас играет» для Icecast2 с использованием JSON

    Здравствуй, Хабр!
    Сегодня мне пришлось столкнуться с проблемой отображения текущего трека и основной информации на страничке с радио.
    В поисках оптимального способа отображения я везде натыкался на жуткие PHP-скрипты, которые тупо парсят страницу статуса Icecast. Более того, на одном из форумах об интернет-радио я наткнулся на очень интересный вопрос — «а зачем вы дёргаете пыху?». Действительно, зачем?
    И я решил во что бы то ни стало сделать выдачу информации о станции в формате JSON, а заодно и поделиться со всеми своими идеями.

    Итак, что нам для этого потребуется: собственно сервер, XSLT-файл для Icecast, скрипт JS для разбора данных и один файлик PHP (позже расскажу зачем).

    Шаг первый: настраиваем Icecast

    Сервер Icecast2 позволяет создавать пользовательские файлы в формате XSL для вывода информации о станции. Кстати говоря, базовая страничка тоже написана в этом формате.
    Для начала узнаем, где icecast ищет свои файлы. Этот путь прописан в файле настроек (по умолчанию это /usr/share/icecast2).
    Переходим в каталог /usr/share/icecast2/www и создаём там файл info.xsl — файл, который будет выдавать информацию о точках монтирования.
    Методом проб и ошибок я составил XSLT-файл, генерирующий валидный JSON-код с информацией о точках монтирования, представленной в виде именованных объектов:

    UPDATE: Парсер Хабрахабра чуть испортил код. Вроде исправил.
    <xsl:stylesheet xmlns:xsl = "http://www.w3.org/1999/XSL/Transform" version = "1.0" >
    <xsl:output omit-xml-declaration="yes" method="text" doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" indent="no" encoding="UTF-8" /><xsl:strip-space elements="*"/>
    
    <xsl:template match = "/icestats" >
    {<xsl:for-each select="source">
    "<xsl:value-of select="@mount" />":
          { <!-- составляем информацию о точке монтирования -->
          "name" : "<xsl:value-of select="server_name"/>",
          "listeners" : "<xsl:value-of select="listeners" />",
          "description" : "<xsl:value-of select="server_description" />",
          "title" : "<xsl:value-of select="title" />",
          "genre" : "<xsl:value-of select="genre" />",
          "url" : "<xsl:value-of select="server_url" />"
    }<xsl:if test="position() != last()"><xsl:text>,</xsl:text></xsl:if> <!-- проверяем, последний ли объект, если нет - ставим запятую -->
        </xsl:for-each>
    }
      </xsl:template>
    </xsl:stylesheet>
    


    Чтобы получить информацию, обращаемся к серверу в вот таком формате: example.com:8000/info.xsl

    В ответ получаем структурированную информацию обо всех существующих точках монтирования. В моём случае это выглядит так:
    {
    "/ns":
          {
          "name" : "Nyan-nyan :3",
          "listeners" : "3",
          "description" : "This is sparta~",
          "title" : "Freaking On Shpongle - Dorset Perception Remix",
          "genre" : "Kircore",
          "url" : "http://*********.com"
          }
    }


    Шаг 2: настройка «прокси»

    Информацию-то мы сгенерировали, но вот беда: при помощи JavaScript нельзя обращаться к другому домену или даже порту. Не беда, сделаем вот такой вот скрипт-«переходник»:
    <?php
    $s = file_get_contents("http://example.com:8000/info.xsl");
    echo($s);
    ?>


    Думаю, PHP будет всяко легче просто запросить и отдать, чем запросить, распарсить, вытащить регулярками нужные данные и потом только отдать информацию. Назовём этот скрипт «get.php» и положим на сервер в удобное место.

    Шаг 3: настройка JavaScript

    Итак, теперь мы можем и генерировать, и принимать информацию, дело за малым — вывести её пользователю.
    Здесь я ограничусь кодом с комментариями к нему:
    
    // Функция для упрощения написания, задаёт содержимое элементу DOM. Лень - двигатель прогресса!
    function set (id, dat)
    {
    	var d = document.getElementById(id);
    	d.innerHTML = dat;
    }
    
    
    function getXmlHttp() // получаем объект XMLHttpRequest, код взят из многочисленных примеров
    {
      var xmlhttp;
      try {
        xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
      } catch (e) {
        try {
          xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
        } catch (E) {
          xmlhttp = false;
        }
      }
      if (!xmlhttp && typeof XMLHttpRequest!='undefined') {
        xmlhttp = new XMLHttpRequest();
      }
      return xmlhttp;
    }
    
    
    function req () // запрос данных
    {
    	var xmlhttp = getXmlHttp()
    	xmlhttp.open("GET", "get.php", true); // просим данные у сервера в асинхронном режиме
    	xmlhttp.onreadystatechange = function() 
    		{
    		  if (xmlhttp.readyState == 4) {
    			 if(xmlhttp.status == 200)
    			   processResult(xmlhttp.responseText); // отдаём на обработку
    		}
    	};
    	xmlhttp.send(null);
    }
    
    
    function processResult (res) // обработка входящих данных
    {
    	var csRes = eval("(" + res + ")"); // да-да, тут должен использоваться jQuery, ибо безопасность и так далее. Но, так как это просто скрипт с примером, не буду ударяться в подробности.
    	var a = []; // пустой массив, для дальнейших действий
    
    // У меня на радио два маунтпоинта - собственно эфир (/stream) и тот, который играет, пока нет диджеев (/ns).
    	if (csRes["/stream"] != null) // Диджей в эфире?
    	{
    		a = csRes["/stream"]; // если да, работаем с данными от него
    		set("sName", "прямой эфир"); // графа "режим" на страничке радио
    	}
    	else // иначе берём данные от точки нон-стоп
    	{
    		a = csRes["/ns"];
    		set("sName", "Non-Stop (без диджея)"); // графа "режим" на страничке
    	}
    	
    	set("trackholder", a["title"]); // Отображаем текущий трек
    
            // Задаём все нужные нам поля (жанр, кол-во слушателей, описание станции и т.д.)
    	set("sGenre", a["genre"]);
    	set("sListeners", a["listeners"]);
    	set("sDescr", a["description"]);
    	
    	setTimeout("req()", 15000); // Если всё прошло удачно, через 15 секунд обновим информацию о станции
    // Здесь вместо setInterval используем setTimeout, чтобы при неработающем icecast скрипт не просил данные впустую.
    }
    
    req(); // первый запрос
    


    Шаг 4: настройка странички

    Здесь всё проще простого. Верстаем страничку, задаём элементы div с именами, используемыми в скрипте выше и после этих элементов указываем ссылку на скрипт:
    ...
    <div id="sDescr">тут описание</div>
    <script type="text/javascript" src="./track.js"></script>
    


    Итог

    А в итоге мы имеем менее загруженный сервер, более переносимый формат вывода информации (например, для мобильных приложений, написанных для прослушивания вашей станции). Зная специфику XSLT-файлов для Icecast можно написать ещё великое множество интересных вещей для этого замечательного открытого медиасервера.
    В конечном итоге у меня получилась симпатичная страничка с информацией о текущем треке:


    При нажатии на строчку с треком можно посмотреть информацию о самой станции:


    Вся эта информация обновляется без перезагрузки страницы.
    Share post

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 26

      +1
      Информацию-то мы сгенерировали, но вот беда: при помощи JavaScript нельзя обращаться к другому домену или даже порту.

      JSONP
        –1
        Но как сделать с JSONP автообновление?
          +1
          Так же. А в чем проблема-то?

          Только вместо ajax используем jsonp.
            0
            Ну, вообще, я упор делал на генерацию валидного JSON (ибо я реально долго промучился с этим) и как принять в общем виде. А там уже — кто во что горазд.
          +1
          Это я писал тот пост «почему вы дергаете статистику с помощью PHP?»
          Как-раз jsonp и пользовал сначала.
            0
            Очень приятно вас видеть тут :D
          0
          Я в свое время просто пар ил последнюю строку лога ) и мне вполне хватало )
            0
            Когда я решал такую же задачу, я смог добиться реалтаймового обновления: по факту информация о песне обновлялась даже перед тем, как она менялась в потоке. использовали php, модуль inotify к php и логи icecast2
              0
              Задача статьи состояла в том, чтобы обойти парсинг средствами PHP.
              Ведь гораздо легче сгенерировать код самим icecast'ом, чем парсить файло.
                0
                я не парсил ничего в php. у меня был запущен демоном конвейер из tail -f и awk, который разбирал общий лог по каналам, поэтому никаких разборов в момент запроса не было
                  +1
                  А задача моего поста на том форуме была вообще обойтись без пхп.
                  Например для использования на статических сайтах))
                    0
                    Задача этого — то же самое. Здесь просто логическое ударение на слове «обойти», а не на «средствами PHP», затем и выделил.
                    В принципе, если бы не полтики безопасности JS, можно было бы вообще без проксирующего компонента обойтись, более того, как сказано ранее, apache2 и nginx сами умеют проксировать.
                      0
                      На Github можно найти кастомные версии айсика который почти всю статистику умеет отдавать сразу в формате json и нинадо никаких xsl делать.
                      Лучше всего все-же парсить логи или постить из айсика в бд(если нужна информация о слушателях) ибо по HTTP теребить айскаст лишний раз не очень хорошо. Каждый раз когда мы подключаемся к нему чтобы получить статистику, мы занимаем слот слушателя т.к. в айскасте кол-во клиентов учитывается общее будь то слушатель и посетитель странички со статистикой. Если данные о кол-ве слушателей не нужны, то лучше узнавать играющий трек от автодиджея или попросить автодиджея постить в last.fm или twitter и оттуда дергать статистику через их API — рельно даст серверу продохнуть. Можно еще поставить на крон чтобы играющий трек и кол-во слушателей обновлялись например раз в несколько секунд, и все это дампилось в файлик stats.json и клалось в доступное место и тогда уже этот статичный файл раздавать клиенту с высокой скоростью.
                        0
                        О CRON тоже мысль была. И, думаю, её и сделаю.
                0
                Зачем нужен пункт 2? Апач, нгникс и лайти умеют проксировать сами.
                  0
                  Как обстоят дела с разными кодировками? Сам столкнулся с проблемой, когда от одной радиостанции данные приходили в windows-1250 (да, именно в 1250 а не в 1251). Собственно я этот процесс до конца не довёл, так как сменил ту работу, но если есть нормальное решение — было бы интересно его узнать.
                    0
                    Вот с этим я пока не разобрался.
                    Я транслирую поток с помощью SAM Broadcaster, но у него нет нигде параметра «кодировка тэгов».
                    0
                    Чет не работает у меня это. Could not parse XSLT file.
                      0
                      Значит неправильно XSL составили.
                      0
                      Также появилась ошибка Could not parse XSLT file, вылечилось удалением тега

                      <xsl:value-of select="<hh user=mount>" />
                      

                      Для валидного выполнения скрипта просто прописал вместо нее

                      "/radio":
                      

                      (точка монтирования у нас все равно одна)

                      Далее, в js файле getXmlHttp упрямо выдавал undefined. Вылечилось полной заменой функции getXmlHttp() на валидный кроссбраузерный вариант:

                      function getXmlHttp() {
                        if (typeof XMLHttpRequest === 'undefined') {
                          XMLHttpRequest = function() {
                            try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); }
                              catch(e) {}
                            try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); }
                              catch(e) {}
                            try { return new ActiveXObject("Msxml2.XMLHTTP"); }
                              catch(e) {}
                            try { return new ActiveXObject("Microsoft.XMLHTTP"); }
                              catch(e) {}
                            throw new Error("This browser does not support XMLHttpRequest.");
                          };
                        }
                        return new XMLHttpRequest();
                      }
                      
                        0
                        «Could not parse...» выдавалось потому, что Хабрахабр заменял (собака)mount на ссылку на хабраюзера.
                        Вместо этого рекомендую использовать jQuery, а не изобретать велосипед, как я.
                        Собственно, улучшенная версия этого JS здесь:
                        nyanserver.com/asset/engine/track.js
                        0
                        xsl:output следует добавить media-type=«application/json» что бы MIME для JSON был верным
                          0
                          а лучше вот так
                          media-type="application/json; charset=utf-8"
                          


                          У меня только после этого стала нормально кодировка в Android`е приходить
                          0
                          А для кроссдоменного запроса просто делаем так:
                          callback(
                          {
                          "/ns":
                                {
                                "name" : "Nyan-nyan :3",
                                "listeners" : "3",
                                "description" : "This is sparta~",
                                "title" : "Freaking On Shpongle - Dorset Perception Remix",
                                "genre" : "Kircore",
                                "url" : "http://*********.com"
                                }
                          }
                          );
                          


                          а потом на javascript тут с jquery пример:
                          $.getJSON('http://example.com:8000/info.xsl', function(data) {
                          //данные в data
                          });
                          
                            0
                            ребзя, дайте пример кода на вывод данных, потому что javascript не фурычит, или я криворукий и не знающий оного…
                              0
                              Class на PHP: https://github.com/xmoonlight/Icecast-Song-Info-php

                              Only users with full accounts can post comments. Log in, please.