Автоматизация работы интернет-радио на Linux

Привет, `whoami`.

В этом посте я расскажу тебе об одном из методов автоматизации интернет-вещания – не самом надежном, но самом бюджетном. Сразу предупреждаю, что эта система заточена на использование ее под Linux, хотя с помощью знакомого многим «столярного инструмента», можно и под Windows реализовать. Эта статья расчитана на начинающих IT-шников, поэтому многие моменты я постарался «разжевать». Как у меня это получилось, решать тебе, мой дорогой читатель, но если мне удалось заинтересовать тебя, прошу под кат.

Предисловие


Итак, сначала позволю себе описать цели, которые я перед собой поставил:
1. Вещание нон-стоп 24/7/365 (не считая отключений электричества в доме).
2. Вещание по расписанию. То есть в определенный временной промежуток дня в эфире должны звучать композиции определенного стиля/жанра.
3. Поддержка «горячего» подключения и отключения ведущего/диджея.
4. Требования к сайту:
4.a. Скромная реализация голосования за звучащие композиции и, соответственно, рейтинг TOP-20/30/сколько_угодно.
4.b. Информация о текущем треке, о текущем стиле/жанре и, если нужно, о количестве слушателей.

Теперь немного о том, что было у меня в «заначке» (точнее, в кладовке):
— домашний компьютер 2003 года, AMD Athlon 1,8 GHz, который уже давно работает как домашний сервер (кстати, я снизил тактовую частоту до 1,1 GHz для экономии электроэнергии);
— операционная система Gentoo Linux;
— доступ в глобальную сеть ~10Mbit/s + выделенный IP;

Настройка сервера Icecast


Итак, поехали. Я не буду описывать установку программ, т.к. в большинстве дистрибутивов они доступны в репозитариях и устанавливаются/собираются одной командой.

В качестве сервера был выбран Icecast 2.3.2, в качетсве source-клиента для нон-стопа – ices (версию не помню).

После установки нужно настроить Icecast следующим образом.

Файл /etc/icecast2/icecast.xml:
<icecast>
    <limits>
	<sources>2</sources>
	<burst-size>32768</burst-size>
	<threadpool>5</threadpool>
    </limits>
    <authentication>
        <admin-user>admin</admin-user>
        <admin-password>ВАШ_ПАРОЛЬ</admin-password>
    </authentication>
    <directory>
        <yp-url-timeout>15</yp-url-timeout>
        <yp-url>http://dir.xiph.org/cgi-bin/yp-cgi</yp-url>
    </directory>
    <directory>
        <yp-url-timeout>15</yp-url-timeout>
        <yp-url>http://www.oddsock.org/cgi-bin/yp-cgi</yp-url>
    </directory>
    <hostname>ВАШ_IP_АДРЕС</hostname>
    <listen-socket>
        <port>ПОРТ_ДЛЯ_СЕРВЕРА</port> <!— Обычно 8000 -->
    </listen-socket>
    <fileserve>1</fileserve>
 <!--Далее, нужно прописать пути к:
 базовому каталогу Icecast, к каталогу для логов, к каталогу, в котором будут хранится xml-файлы с информацией о сервере, к каталогу администрирования -->
    <paths>
	<basedir>/usr/share/icecast</basedir>
	<logdir>/var/log/icecast</logdir>
	<webroot>/home/www/icecast</webroot>
	<adminroot>/home/www/icecast/admin</adminroot>
	<alias source="/" dest="/status.xsl"/>
    </paths>
    <logging>
        <accesslog>access.log</accesslog>
        <errorlog>error.log</errorlog>
      	<loglevel>3</loglevel>
    </logging>
 <!-- Сначала описание нон-стопа. Вообще половину из этого можно не описывать, т.к. вся информация берется из конфига ices, но я для порядка описал. -->
    <mount>
        <mount-name>/non-stop</mount-name>
        <password>ПАРОЛЬ_НОН-СТОПА</password>
        <max-listeners>МАКС_КОЛ_ВО_СЛУШАТЕЛЕЙ</max-listeners>
        <charset>cp1251</charset>
        <public>0</public>
        <stream-name>НАЗВАНИЕ_РАДИО</stream-name> <!--  например, Habr.FM Non-Stop -->
        <stream-description>24/7 Non-stop music</stream-description>
        <stream-url>АДРЕС_САЙТА</stream-url>
        <genre>Electronic</genre> <!-— Жанр-->
        <bitrate>128</bitrate>
        <type>audio/mpeg</type>
        <subtype>mp3</subtype> <!-- Вещаем в mp3, чтобы не было проблем. -->
        <hidden>0</hidden>
    </mount>
<!-- Теперь нужно описать mountpoint для диджеев, чтобы они могли подключаться в любое время -->
    <mount>
        <mount-name>/live</mount-name>
        <password>ПАРОЛЬ_ДЛЯ_ДИДЖЕЕВ</password>
        <max-listeners>100</max-listeners>
<!-- А вот это очень важно: предполагается, что слушатели подключаются к серверу именно по mountpoint /live, но если диджея в эфире нет, их автоматически (и прозрачно) перенаправляют на нон-стоп. Если диджей внезапно появляется в эфире, слушателей опять же автоматически перенаправляют на поток /live. Поэтому он должен быть основным. -->
        <fallback-mount>/non-stop</fallback-mount>
        <fallback-override>1</fallback-override>
        <fallback-when-full>0</fallback-when-full>
        <charset>cp1251</charset>
        <public>1</public>
        <stream-name>НАЗВАНИЕ_РАДИО</stream-name>
        <stream-description>ОПИСАНИЕ_РАДИО</stream-description>
        <stream-url>АДРЕС_САЙТА</stream-url>
        <genre>Electronic</genre> <!-- Опять жанр -->
        <bitrate>128</bitrate>
        <type>audio/mpeg</type>
        <subtype>mp3</subtype>
        <hidden>0</hidden>
    </mount>
    <security>
        <chroot>0</chroot>
        <changeowner>
		<user>icecast</user> <!-- Пользователь, от имени которого будет запущен Icecast -->
		<group>nogroup</group> <!-- и группа -->
        </changeowner>
    </security>
                                                        
</icecast>

Настройка ices


С настройкой ices все проще:

Файл /etc/ices.conf:
<ices:Configuration xmlns:ices="http://www.icecast.org/projects/ices">
<Playlist>
<!-- Здесь нужно указать файл плейлиста (о его формировании написано далее). -->
	<File>/home/PUBLIC/Music/playlist.m3u</File>
	<Randomize>0</Randomize> <!-- Рандомизацию нужно отключить, т.к. она будет реализовываться отдельно. -->
	<Type>builtin</Type>
	<Module>ices</Module>
	<Crossfade>1</Crossfade> <!-- Если ресурсы позволяют, можно включить (см. далее <Reencode>) -->
</Playlist>

<Server>
	<Hostname>localhost</Hostname>
	<Port>ВАШ_ПОРТ</Port>
	<Password>ВАШ_ПАРОЛЬ</Password>
	<Protocol>http</Protocol>
</Server>

<Execution>
	<Background>1</Background>
	<Verbose>1</Verbose>
	<Base_Directory>/tmp</Base_Directory>
</Execution>

<Stream>
	<!-- Копия раздела <Server> (см. выше) -->
	<Server>
        	<Hostname>localhost</Hostname>
        	<Port>ВАШ_ПОРТ</Port>
        	<Password>ВАШ_ПАРОЛЬ</Password>
        	<Protocol>http</Protocol>
	</Server>
	<Mountpoint>/non-stop</Mountpoint>
<!-- А вот эту информацию следует заполнить аккуратно, т.к. она будет видна слушателям. --> 
	<Name>НАЗВАНИЕ_РАДИО</Name>
	<Genre>Electronic</Genre> <!-- Жанр -->
	<Description>24/7 Non-stop music</Description>
	<URL>АДРЕС_САЙТА</URL>
	<Bitrate>128</Bitrate>
	<Public>1</Public>
<!-- Теперь немного о грустном. ices поддерживает замечательную функцию кроссфейдинга и перекодирования на лету, но это сильно нагрузило мой сервер, и я отказался от такой фичи. -->
	<Reencode>0</Reencode>
	<Samplerate>-1</Samplerate>
	<Channels>2</Channels>
</Stream>
</ices:Configuration>

Итак, сервер Icecast настроен, и его уже можно запустить (обычно, /etc/init.d/icecast start).
Ices тоже настроен, однако его запускать пока рано, ибо нет плейлиста для нон-стопа.
Собственно это сейчас и исправим…

Формирование плейлистов


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

Структура каталога проста:

Music
--Genre1
----File1.mp3
----File2.mp3
----…
----playlist.m3u
--Genre2
----File1.mp3
----File2.mp3
----…
----playlist.m3u

playlist.m3u

Т.е. в основном каталоге Music имеется несколько подкаталогов для различных стилей (помнишь, я говорил о расписании нон-стопа?). В каждом таком подкаталоге расположены mp3-файлы и один плейлист.

Итак, начнем с маленького BASH-скрипта для формирования плейлистов для всех стилей.

Файл music_find.sh (спасибо differentlocal за упрощение моего скрипта)
#!/bin/bash

# Каталог для хранения музыки
MUSICDIR=/home/PUBLIC/Music

cd $MUSICDIR
for i in *; do
  cd $MUSICDIR/$i
  find `pwd` -name "*.mp3" > playlist.m3u
done

Примечание: для людей, далеких от электронной музыки, поясняю, что Breaks, Chill, Hardcore это как раз стили электронной музыки.

И так как мой коллега далек от Linux, и не может зайти по SSH и запустить этот скрипт после обновления содержимого каталога, было решено поручить эту миссию Великому Крону:
# crontab -e
10,40 *    *    *    *   /root/scripts/radio/music_find.sh

Теперь через каждые 30 минут скрипт будет запускаться и обновлять плейлисты.

Однако, если ты еще не забыл, в конфиге ices был указан «главный» плейлист, который в этом скрипте не формируется.

Тут нужно вспомнить о расписании. Идея проста до безобразия: в определенное время (согласно расписанию эфира) копировать с заменой нужный плейлист в корневой каталог музыки (в моем случае /home/PUBLIC/Music). Сначала я думал реализовать расписание полностью на BASH, но потом вспомнил, что добрый Крон всегда готов нам помочь и выполнить всю грязную работу за нас. Таким образом возник скрипт, реализующий изменение плейлиста согласно расписанию.

Однако, сначала лирическое отступление…

Помнишь, при конфигурировании ices я отключил функцию рандомизации? Тебя наверное интересует, зачем? Причин две:
1. Бог его знает, как ices выполняет рандомизацию. Я привык (уж извините) максимально контроллировать ситуацию. Поэтому лучше сделать рандомизацию так, как сам хочешь, чтобы душа таки успокоилась.
2. Если ты читаешь эту статью, тебе, наверное, знаком термин «джингл». Так вот, если предполагается вставка джинглов в эфир (например, через каждые три трека), тут ices бессилен, т.к. он не знает, что это такое. Эта еще одна причина для написания собственной программы рандомизации.

BASH, конечно, штука хорошая, но для данной задачи я выбрал язык C++. Ниже приведен исходный код программы на C++, которая просто считывает содержимое созданного ранее плейлист-файла (имя передается ей в качестве параметра), перемешивает, и записывает в тот же файл.

Исходный код программы рандомизации:
#include <iostream>
#include <fstream>
#include <cstdlib>
#include <ctime>
#include <string>
#include <vector>

using namespace std;

int main(const int argc, const char *argv[])
{

   if (argc<2)
   {
     cout << "ERROR: no argument recieved." << endl;
     return 1;
   }

   vector<string> list;
   string line;

   ifstream infile(argv[1]);
   
   if (infile.fail())
     return 1;

   cout << "Using file: " << argv[1] << endl;

   while (!infile.eof())
   {
     getline(infile,line);
     list.push_back(line);
   }
   infile.close();

   cout << "End of file reached." << endl;

   int n = list.size();
   if (n>1)
   {
	 cout << "Begin shuffle." << endl;
         srand(time(0));
	 string temp;
         for (int i=0; i<(n-1); i++)
	 {
           int r = i + (rand() % (n-i));
           temp = list[i];
           list[i] = list[r];
           list[r] = temp;
	 }
         cout << "Finished shuffle." << endl;
	 
	 ofstream outfile(argv[1]);
	 
	 for (int i=0; i<n; i++)
	   outfile << list[i] << endl;
	 outfile.close();
	 
         cout << "File succuessfully updated." << endl;
	 return 0;
   }

   return 1;
}

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

Реализация расписания


Итак, лирическое отступление закончено, вернемся к расписанию:

Файл playlist_update:
#!/bin/bash

MUSICDIR=/home/PUBLIC/Music
	
cd $MUSICDIR
cp -f $1/playlist.m3u playlist.m3u

/root/scripts/radio/shuffle playlist.m3u >> /dev/null

echo "$1" > /home/www/HabrFM.ru/genre_non-stop.txt

if ps -A | grep ices
then
  killall -HUP ices
else
  /etc/init.d/ices start
fi

Сначала скрипт выполняет копирование с заменой нужного плейлист-файла, затем запускает программу рандомизации, записывает в текстовый файл текущий жанр (зачем, узнаешь позже)
и посылает сигнал «перечитать плейлист» ices. Если ранее ices завершил работу, скрипт его снова запустит. При этом важно знать, что ices начнет проигрывать новый плейлист только, когда закончит проигрывать текущий трек. Поэтому расписание будет выглядеть не очень элегантно, зато его можно поручить нашему другу Крону.

Привожу пример со своими стилями:
crontab -e
  58    01   *    *    *   /root/scripts/radio/playlist_update Breaks
  58    03   *    *    *   /root/scripts/radio/playlist_update Chill
  58    09   *    *    *   /root/scripts/radio/playlist_update Dance
  58    14   *    *    *   /root/scripts/radio/playlist_update House
  58    17   *    *    *   /root/scripts/radio/playlist_update Trance
  58    21   *    *    *   /root/scripts/radio/playlist_update Hardstyle
  58    23   *    *    *   /root/scripts/radio/playlist_update Hardcore

Та-да-дам! Вот теперь строительство вещания по расписанию закончено. Когда нет диджея, играет по расписанию нон-стоп, когда появляется диджей — слушателей автоматически перенаправляет на его «эфир». Однако, если ты помнишь, еще остались нереализованные задачи, касающиеся сайта.

База данных музыкальных композиций


Ах да, совсем забыл. Для реализации системы голосования я использовал БД MySQL. Так что потрудись, друг мой, обзавестись таковым, если еще этого не сделал.

Итак, нам нужно хранить в БД информацию о всех треках, которые присутствуют в «корневом музыкальном каталоге». Нужно создать базу данных (в моем примере это radio), а в ней две таблицы следующей структуры:

Таблица songs
id INT(11) AUTO_INCREMENT PRIMARY_KEY
Genre VARCHAR(15)
Title VARCHAR(100)
Filename VARCHAR(200)
Rate INT(11)

Таблица votes
id INT(11)
ip VARCHAR(16)

Теперь о печальном. Во-первых, далеко не у всех mp3-файлов (даже у лицензионных) корректные ID3-теги, а у большинства их вообще нет. Во-вторых, я не смог найти скрипт для чтения ID3-тегов из файлов. Поэтому пришлось пойти на некоторые жертвы. А именно: с помощью программы TagScanner вручную править ID3-теги всех файлов, а затем с помощью этой же программы переименовывать файлы согласно их уже корректным ID3-тегам. Я выбрал следующий шаблон:

<счетчик>. <Исполнитель> — <Композиция>.mp3

Я не буду описывать в этой статье работу с этой программой. Скажу лишь, что принципиально важно сохранять ВСЕ ID3-теги НЕ в UTF-8. В настройках программы есть соответствующая опция. кроме того, нужно заменить все символы ‘&’ на, например, “and”. Программа позволяет делать это сразу для всех файлов.

ОК, предположим, что у нас теперь есть корректные имена файлов, соответствующие шаблону, и база данных правильной структуры. Далее, собственно, еще один скрипт, но уже на PHP (так что не забудь на досуге установить и его).

Файл db_update.php:
#!/usr/bin/php

<?php

$MUSICDIR="/home/PUBLIC/Music";
$hostname = "localhost";
$username = "radio"; // Имя пользователя для БД
$password = "12345"; // И пароль
$dbName = "radio"; // Имя БД
mysql_connect($hostname,$username,$password) OR die("Can't connect to database.");
mysql_select_db($dbName) or die(mysql_error());

$Gen = array('Dance','House','Trance','Hardstyle','Hardcore','Chill','Breaks','Pumping');

$sql = "SELECT * FROM radio.songs";
$all = mysql_query($sql);
$num_before = mysql_num_rows($all);
echo "There are $num_before records in database.\n";

echo "\n";

echo "Searching for non-existing file names...\n";
$deleted = 0;
while($row = mysql_fetch_array($all, MYSQL_ASSOC)) {
  $id = $row['id'];
  $db_filename = $row['Filename'];
  $exist = @fopen($db_filename,"r");
  if (!$exist) {    
    echo "Deleting: [$id] $db_filename\n";
    $sql = "DELETE FROM radio.songs WHERE id=$id";
    mysql_query($sql);	
    $deleted++;  
  }
}

if (!$deleted) {
  echo "Nothing deleted.\n";
}
else {  
  echo "Total deleted: $deleted records.\n";
}

echo "\n";

$added = 0;
echo "Searching for new tracks...\n";
for ($i=0;$i<count($Gen);$i++) {
  $genre=$Gen[$i];
  $fp = fopen("$MUSICDIR/$genre/playlist.m3u","r");
  while (!feof($fp)) {  
    $filename = fgets($fp);  
    if (strpos($filename,".mp3")) {    
      $filename = substr($filename,0,strlen($filename)-1);
      $sql = sprintf("SELECT * FROM radio.songs WHERE Filename='%s'",mysql_real_escape_string($filename));    
      $res = mysql_query($sql);    
      $num = mysql_num_rows($res);    
      if ($num == 0) {  
        $title = strstr($filename," "); 
        $start = strpos($filename,". ")+2;
        $len = strpos($filename,".mp3") - $start; 
        $title = substr($filename,$start,$len);
        $filename = substr($filename,0,strpos($filename,".mp3")+4);
        $sql = sprintf("INSERT INTO radio.songs ( `id`, `Genre`, `Title`, `Filename`, `Rate` ) VALUES ( NULL, '$genre', '%s', '%s', '0' )", mysql_real_escape_string($title),mysql_real_escape_string($filename));
        mysql_query($sql) or die(mysql_error());
        $added++;
        echo "Adding $filename\n";
      }  
    }
  }
}

if (!$added) {
  echo "Nothing added.\n";
}
else {
  echo "Total added: $added records.\n";
}

echo "\n";

$sql = "SELECT * FROM radio.songs";
$all = mysql_query($sql);
$num_after = mysql_num_rows($all);
if ($num_before == $num_after) {
  echo "There are still $num_after records in database.\n";
}
else {
  echo "There are $num_after records in database.\n";
}

mysql_close();

?>

Приведенный выше скрипт сначала удаляет из БД записи о треках, которых уже нет в соответствующем каталоге, а затем добавляет новые, если они есть.

Было бы логично запускать этот скрипт сразу после составления плейлистов, поэтому добавим одну строчку в файл music_find.sh (предварительно, заменив путь на свой):

/root/scripts/radio/db_update.php

Теперь при составлении плейлистов автоматически будет обновляться информация в БД.

Вторую таблицу (votes) оставлю на закуску.

Сбор информации о вещании


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

Файл icecast_status.php:
#!/usr/bin/php

<?php 

$STATS_FILE = 'http://IP_АДРЕС_СЕРВЕРА:ПОРТ_СЕРВЕРА/status.xsl';
$DOCROOT = '/var/www/HabrFM.ru'; // Корень сайта
$hostname = "localhost";
$username = "radio"; // Имя пользователя для БД
$password = "12345"; // И пароль
$dbName = "radio"; // Имя БД
mysql_connect($hostname,$username,$password) OR die("Can't connect to database.");
mysql_select_db($dbName) or die(mysql_error());

for($i=1;$i<13;$i++) {

$fp = fopen($STATS_FILE,'r'); 
if(!$fp) { die("Unable to connect to Icecast server."); } 
$stats_file_contents = ''; 
while(!feof($fp)) { $stats_file_contents .= fread($fp,1024); } 

fclose($fp); 

$radio_info = array(); 
$radio_info['genre'] = '';
$radio_info['listeners'] = ''; 
$radio_info['now_playing'] = ''; 

$temp = array(); 
$search_for = "<td\s[^>]*class=\"streamdata\">(.*)<\/td>"; 
$search_td = array('<td class="streamdata">','</td>'); 
if(preg_match_all("/$search_for/siU",$stats_file_contents,$matches)) { 
   foreach($matches[0] as $match) { 
      $to_push = str_replace($search_td,'',$match); 
      $to_push = trim($to_push); 
      array_push($temp,$to_push); 
   } 
} 

$radio_info['listeners'] = $temp[5];
$radio_info['now_playing'] = $temp[9]; 

if(strpos($stats_file_contents,'/live')) {
    $radio_info['genre'] = "DJ On-Air";
}
else {
    $fp = fopen("$DOCROOT/genre_non-stop.txt","r");
    $radio_info['genre'] = fgets($fp);
    fclose($fp);
    $radio_info['genre'] = substr($radio_info['genre'],0,strlen($radio_info['genre'])-1);
}

if ($radio_info['genre'] == "DJ On-Air"){
  $rate = "1000+";
}
else {
  $sql = sprintf("SELECT * FROM songs WHERE ( Genre='%s' AND Title='%s' )", mysql_real_escape_string($radio_info['genre']), mysql_real_escape_string($radio_info['now_playing']));
  $res = mysql_query($sql) or die();
  $row = mysql_fetch_array($res, MYSQL_ASSOC);
  $rate = $row['Rate'];
  $id = $row['id'];
}

$fp = fopen("$DOCROOT/now_playing.txt","w");
fputs($fp,$radio_info['now_playing']);
fclose($fp);

$fp = fopen("$DOCROOT/id.txt","w");
fputs($fp,$id);
fclose($fp);

$fp = fopen("$DOCROOT/listeners.txt","w");
if ($radio_info['listeners'] > 0) { fputs($fp,'<span style="color:green; font-weight: bold;">'.$radio_info['listeners'].'</span>'); }
else { fputs($fp,'<span style="color:black; font-weight: bold;">'.$radio_info['listeners'].'</span>'); }
fclose($fp);

$fp = fopen("$DOCROOT/genre.txt","w");
fputs($fp,$radio_info['genre']);
fclose($fp);

$fp = fopen("$DOCROOT/rate.txt","w");
if ($rate > 0) { fputs($fp,'<span style="color:green; font-weight: bold;">+'.$rate.'</span>'); }
if ($rate < 0) { fputs($fp,'<span style="color:red; font-weight: bold;">'.$rate.'</span>'); }
if ($rate == 0) { fputs($fp,'<span style="color:black; font-weight: bold;">'.$rate.'</span>'); }
fclose($fp);

sleep(5);

}
?>


Этот скрипт нужно запускать каждую минуту, причем он выполняется 12 раз с задержками по 5 секунд. Этот «велосипед» связан с тем, что наш друг Крон не подразумевает о существовании единиц измерения времени меньше, чем 1 минута. А нам нужно обновлять эту информацию хотя бы раз в 5 секунд.

Что же, попросим Крона (в последний раз) запускать этот скрипт каждую минуту:

crontab -e
*/1   *    *    *    *   /root/scripts/radio/icecast_status.php

ОК, теперь в корне нашего сайта лежат аж 5 замечательных файлов:
• now_playing.txt — исполнитель и название текущего трека;
• id.txt — уникальный номер в БД текущей композиции;
• genre.txt — стиль трека, если играет нон-стоп, или строка «DJ On-Air”, если в эфире диджей;
• listeners.txt — количество слушателей (с учетом HTML-форматирования: если больше 0, то зеленым цветом, если ноль — черным);
• rate.txt — рейтинг трека (тоже с учетом HTML-форматирования).

Ух ты, мы уже почти получили все, что хотели (или то, чего я хотел). Осталось реализовать голосование и, собственно, показ нужной информации на сайте.

Голосование за композиции


Первое, о чем я подумал, когда захотел реализовать голосование — а как блокировать повторные голосования за один трек? Обычно используют Cookies, но я с ними никогда не работал (да, такое тоже бывает), и решил блокировать по IP-адресу. Поэтому в таблице votes всего два поля: id и ip.

Файл vote.php (должен располагаться в корне сайта):
<?php

//Это, надеюсь, в комментариях не нуждается
$DOCROOT = '/var/www/HabrFM.ru';
$hostname = "localhost";
$username = "radio";
$password = "12345";
$dbName = "radio";

mysql_connect($hostname,$username,$password) OR die("Can't connect to database.");
mysql_select_db($dbName) or die(mysql_error());

$fp = fopen("$DOCROOT/id.txt", "r");
$id = fgets($fp);
fclose($fp);

$sql = sprintf("SELECT * FROM votes WHERE ( id='%s' AND ip='%s' )", $id, mysql_real_escape_string($_SERVER['REMOTE_ADDR']));
$res = mysql_query($sql);
$num = mysql_num_rows($res);

if ($num == 0)
{
  
	$type = $_GET['type'];

	if ($type == 'plus') {
	  $sql = sprintf("UPDATE songs SET Rate=Rate+1 WHERE id='%s'", $id);
	}
	else {
		if ($type == 'minus') {
		  $sql = sprintf("UPDATE songs SET Rate=Rate-1 WHERE id='%s'", $id);
		}
		else { 
		  die('Irregular argument.'); 
		}
	}

	if (mysql_query($sql)) {
	  $sql = sprintf("INSERT INTO votes (`id`, `ip`) VALUES ('%s', '%s')", $id, mysql_real_escape_string($_SERVER['REMOTE_ADDR']));
	  mysql_query($sql);
	  echo 'Ваш голос учтен.';
	}
	else {
	  echo 'Произошла ошибка.';
	}
}
else {
  echo "Вы уже голосовали за этот трек.";
}

mysql_close();

?>


Этот скрипт принимает один параметр — вид голосования (за или против). Соответственно, вызов
vote.php?type=plus

добавит 1 рейтинга текущему треку, а
vote.php?type=minus

отнимет 1 рейтинга.

Показ информации на сайте


Итак, голосование — есть. Осталось, разве, что вывести на сайте всю необходимую информацию. Я не буду приводить конкретный HTML-код. Просто напомню, если вдруг ты забыл, о существовании замечательного фреймворка jQuery.

Пусть в нужных местах HTML-кода стоят элементы или с id='now_playing' для названия трека, с id='genre’ для жанра и тд.
Тогда удобно встроить в следующий вызов (конечно, библиотеку jQuery нужно добавить в каталог сайта и подключить):
<script>
function show()	{ 
	$.ajax({ url: "now_playing.txt", cache: false, success: function(html){ $("#now_playing").html(html); }	});
	$.ajax({ url: "genre.txt", cache: false, success: function(html){ $("#genre").html(html); }	});
	$.ajax({ url: "listeners.txt", cache: false, success: function(html){ $("#listeners").html(html); }	});
	$.ajax({ url: "rate.txt", cache: false, success: function(html){ $("#rate").html(html); }	});
}
$(document).ready(function(){ show(); setInterval('show()',5000); });
</script>


И тогда каждые 5 секунд информация на странице будет обновляться (причем, без перезагрузки страницы).

Помнишь, в начале статьи я заявил о показе рейтинга TOP20? Да, это было бы здорово.

Итак, последний скрипт, который выдает таблицу лучших (type=1) или худших (type=2) треков определенного стиля/жанра в следующем формате:
| Композиция | Рейтинг |

Файл top20.php (лучше допилить инструментом под свой сайт):
<?php

$Gen = array('Dance','House','Trance','Hardstyle','Hardcore','Chill','Breaks','Pumping');
//Опять те же строки :)
$hostname = "localhost";
$username = "radio";
$password = "12345";
$dbName = "radio";
mysql_connect($hostname,$username,$password) OR die("Can't connect to database.");
mysql_select_db($dbName) or die(mysql_error());

$genre = $_GET["genre"];
$type = $_GET["type"];

echo "<br>\n";

if (!(in_array($genre,$Gen))) { die("Irregular argument."); }

$sql = sprintf("SELECT Title, Rate FROM songs WHERE (Genre='%s'",mysql_real_escape_string($genre));
if ($type==1) { $sql = $sql . " AND Rate>0) ORDER BY Rate DESC LIMIT 20"; }
else if ($type==2) { $sql = $sql . " AND Rate<0) ORDER BY Rate ASC LIMIT 20"; }
else { die("Irregular argument"); }

$res = mysql_query($sql);

if (mysql_num_rows($res)>0) {
  echo '<table border="1" style="font-family: Verdana,Geneva; font-size: 10;" cellspacing="0" width="100%">';
  echo "\n";
  echo '<tr><td align="left">№</td><td align="left" width="100%">Исполнитель - Трек</td><td align="right">Рейтинг</td></tr>';
  echo "\n";
} 
else {
  echo "Этот рейтинг пока пуст. Вы можете проголосовать за какой-нибудь трек во время его звучания на радио.";
}

$i = 1;
while ($row = mysql_fetch_array($res, MYSQL_ASSOC)) {
  echo "<tr>";
  echo '<td align="left">' . $i . '.</td>';
  echo '<td align="left">' . $row['Title'] . '</td>';
  echo '<td align="right">' . $row['Rate'] . '</td>';
  echo "</tr>\n";
  $i++;
}

if (mysql_num_rows($res)>0) {
  echo '</table>';
}

mysql_close();

?>

Вот и все. Далее нужно встроить этот скрипт в нужное место HTML-кода (можно использовать тот же jQuery, только без setInterval('show()',5000). Но это я оставляю тебе, мой юный друг, в качестве домашнего задания.

Искренне надеюсь, что мой рассказ окажется тебе полезным или просто интересным.
Поделиться публикацией
Ой, у вас баннер убежал!

Ну. И что?
Реклама
Комментарии 31
  • 0
    Статья интересная. Но есть вопрос, что автор понимает под начинающими IT-шниками? Все-таки LAMP, jQuery, Bash, C++ и при этом не считая настройка самой системы никак нельзя отнести к начинающим:) Или я не прав?
    • +1
      Согласен, немного погорячился. Но моя статья затрагивает именно реализацию под Linux, посему человек, не знающий основ администрирования этой операционной системы и не будет ее читать. А если кто-то читает, то он уж в состоянии поднять вебсервер базовой конфигурации. jQuery я привел как пример, можно это и без него реализовать. Код C++ можно просто использовать без изменения.

      Вообще, если человек имеет базовые знания о системе Linux и о том, что такое PHP, сможет просто использовать приведенные коды «без изменений».
      • 0
        Надо будет попробовать, т.к. сама идея мне очень понравилась:) Еще раз спасибо.
        • 0
          Если возникнут вопросы по поводу интернет-радио, пишите. Я этой темой уже больше года занимаюсь.
          • +1
            Советую уделить пару вечеров на изучение bash. Он явно у вас хромает…
            А так статья достаточно интересная.
            • 0
              Тут опыт нужен. Покажите, как сделать то же самое лучше.
              • 0
                Спасибо за совет. Буду совершенствоваться.
              • 0
                С каких пор ротация на радио стала называться рандомизацией? Учите матчасть для начала…
        • 0
          Отличная статья, сам давно интересуюсь интернет-радио.
          • +1
            спасибо за статью.

            PS:
            $filename = substr($filename,0,strpos($filename,".mp3")+4);
            легче через pathinfo
            • +1
              Немного не в тему, но как-то мне понадобилось снимать звук с линейного входа звуковой карты и вещать его в сеть. Может кто-нибудь подскажет как это по проще реализовать?
              • 0
                darkice — An IceCast, IceCast2 and ShoutCast live audio streamer
              • 0
                Как-то массово всех пробило, неделя радио :)
                Относительно недавно тоже сделал радио, правда задумка совсем другая, больше как социальное радио можно назвать. Правда идея не до конца развита, есть нюансы :)
                Суть такая — можно регистрироваться и добавлять свои треки, они автоматом добавляюься в общий плейлист. Вот какраз проблема в том, чтобы среди общей направленности радиостанции не начали проскакивать всякие бутырки. =)

                Не помню почему, но как-то ices прошёл у меня мимо, сделал mpd + icecast.
                • +2
                  Я тихо оставлю ссылку на LiquidSoap, вдруг кому пригодится
                  • 0
                    А вот это интересно. Как-нибудь попробую на нем свою систему реализовать.
                    • 0
                      попробуйте, система очень гибкая, тут вам и работа по расписанию, и куча sources, и возможность их миксовать, и подключение внешнего стрима (скажем — вещание с микрофона можно организовать). Примеров не просто много — их очень много, можно понять основные принципы — а дальше уже и самому нарисовать нужный конфиг
                    • 0
                      я вот только хотел про него написать.
                      а вообще, мне непонятно — в каждом топике про интернет-радио о нем вспоминают, но снова и снова описывают велосипеды.
                    • 0
                      Вау! Это лучшая статья за много времени! Большой респект!
                      • +1
                        Плейлист формируется намного проще:

                        MUSICDIR=/home/PUBLIC/Music

                        cd $MUSICDIR
                        for i in *; do cd $MUSICDIR/$i && find `pwd` -name "*.mp3" > playlist.m3u; done

                        Зачем явно указывать жанры и каждый раз править скрипт?
                      • +1
                        Классная статья, если бы не маленькая ложка дёгтя в виде
                        «Скажу лишь, что принципиально важно сохранять ВСЕ ID3-теги НЕ в UTF-8.»
                        21 век на дворе… когда будет тотальный UTF?
                        Линукоиды и так с ID1 бодались, в котором нельзя было не ANSI символы, но всем походу наплевать…
                        и вы туда же =(
                        • 0
                          Вот именно. Надо принудительно вырезать ID3v1, оставить ID3v2 в UTF-8.
                          • 0
                            так и делали =/ из IDv1 копировали в IDv3, конвертируя в UTF8 и удаляя IDv1.
                        • 0
                          Есть еще такой проект: www.somasuite.org/
                          Заброшенный правда, но в то время когда я этим интересовался он активно развивался.
                          • 0
                            я просто оставлю это здесь
                            code.google.com/p/ardj/
                            • 0
                              просто хороший мануал, собранный с разных источников и собранный в полноценную развернутую статью
                              спасибо…
                              самому приходилось по крупицам по форумам обособить все это
                              • –1
                                Может у кого есть решения для внутреннего радио? Чтобы можно играла музыка, а периодически читались объявления.
                                • 0
                                  Если переписать программу рандомизации плейлиста, можно реализовать все, что угодно.
                                  • 0
                                    Например, сделать отдельный каталог для рекламных аудио записей. Как описано в статье, сгенерировать плейлист. А в программе рандомизации работать с двумя файлами сразу. Например, после трех треков вставлять в плейлист случайную рекламную аудио запись.
                                    • 0
                                      Спасибо за совет, буду делать. Получится что-нибудь толковое — напишу пост
                                  • 0
                                    Спасибо, настроил у себя вещание разных жанров по времени + рандомизацию (ubuntu 11.10 + icecast + ices0) + поддержку «хот» вклинивания ведущего.

                                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                    Самое читаемое