# Веб-радіо в Linux: встановлення сервера Icecast та базове налаштування ротації з Ezstream

Давно планував і нарешті вирішив таки створити власну "лампову" радіо-станцію для локальної мережі Yggdrasil. Інформації про налаштування Icecast в мережі повно, але для мене знайдена інфа виглядала заплутаною: я не розумів, чому окремо розглядається Icecast, Ices, Ezstream та додатковий скриптинг, у той час як мені для початку було потрібно просто зробити тривіальну річ: мотати по колу плейлист музичних файлів.

В темі стрімінгу та аудіо-обробки - я нуб. З цієї причини вирішив написати невеличкий гайд для Debian/Linux, зрозумілий для себе "в минулому" а також практичну нотатку, на випадок якщо завалю сервер та доведеться пригадувати що робив. Згодом, планую також навчитись додавати ретрансляцію етерів новин за розкладом, можливо - пускати в ефір живі стріми, тоді й розширю цей матеріал до серії.

## Icecast

Icecast (https://icecast.org) - це класичний сервер, на базі якого створюється публічний сервіс для слухачів (клієнтів). Його можна порівняти з проксі сервером Nginx для Веб-сайтів, от тільки створений він спеціально для операцій з потоковими даними. Цей сервер ніяк не взаємодіє з медіа-колекціями напряму, він просто отримує на себе сирий потік аудіо (або інших) даних з бекенду (по API) та розподіляє його на активні підключення, виступаючи в ролі хабу, своєрідної "радіо-вежі".

### Встановлення

``` bash
apt install icecast2
```

### Налаштування

Після встановлення пакету з репозиторію, до системи буде автоматично додано сервіс systemd (/etc/init.d/icecast2) та стандартний файл конфігурації (/etc/icecast2/icecast.xml). Зайдемо до останнього та адаптуємо його під свої потреби:

``` bash
nano /etc/icecast2/icecast.xml
```

``` /etc/icecast2/icecast.xml
<icecast>
    <!-- Базові налаштування, я лишив стандартні -->
    <location>Earth</location>
    <admin>icemaster@localhost</admin>
    <limits>
        <clients>100</clients>
        <sources>2</sources>
        <queue-size>524288</queue-size>
        <client-timeout>30</client-timeout>
        <header-timeout>15</header-timeout>
        <source-timeout>10</source-timeout>
        <burst-on-connect>1</burst-on-connect>
        <burst-size>65535</burst-size>
    </limits>

    <authentication>
        <!-- Пароль для бекенд-клієнтів (з ним будемо підключати Ezstream) -->
        <source-password>PASSWORD</source-password>

        <!-- Цей пароль в прикладах не використовується, але заповніть це поле "на потім" -->
        <relay-password>PASSWORD</relay-password>

        <!-- Доступи до Веб-адмінки -->
        <admin-user>admin</admin-user>
        <admin-password>PASSWORD</admin-password>
    </authentication>

    <!-- Адреса хосту для клієнтів.
         Оскільки мій сервер працює на різні мережі,
         я вказав наступним чином: -->
    <hostname>ua</hostname>

    <!-- Важливо вказати, якщо ви плануєте ротацію треків з кириличними назвами в системах Unix -->
    <mount type="default">
       <charset>UTF-8</charset>
    </mount>

    <!-- Параметри для локального запуску сервісу Icecast.
         Тут можна вказати 127.0.0.1 (localhost), якщо з'єднання з сервером проксується через Nginx
         або цей сервер буде локальним в принципі;
         в моєму випадку, я транслюю на пряму з Icecast, на всі мережі IPv6 -->
    <listen-socket>
        <port>8000</port>
        <bind-address>::</bind-address>
    </listen-socket>

    <!-- Політика крос-сайтового скриптингу ACAO/CORS
         у більшості випадків можна лишити стандартно -->
    <http-headers>
        <header name="Access-Control-Allow-Origin" value="*" />
    </http-headers>

    <!-- Вмикає файловий сервер Icecast,
         з якого можуть подаватися статичні файли (графіка, CSS) -->
    <fileserve>1</fileserve>

    <!-- Налаштування шляхів, лишаю стандартними -->
    <paths>
        <basedir>/usr/share/icecast2</basedir>

        <logdir>/var/log/icecast2</logdir>
        <webroot>/usr/share/icecast2/web</webroot>
        <adminroot>/usr/share/icecast2/admin</adminroot>

        <!-- Аліас: у даному випадку,
             перенаправляє запити з http://server:port/ до http://server:port/status.xsl -->
        <alias source="/" destination="/status.xsl"/>
        <!-- Якщо використовується шифрування,
             файл PEM повинен містити у собі приватну та публічну пари!
        <ssl-certificate>/usr/share/icecast2/icecast.pem</ssl-certificate>
        -->
    </paths>

    <logging>
        <accesslog>access.log</accesslog>
        <errorlog>error.log</errorlog>

        <!-- Рівні подій для додавання в accesslog:
             * 4 відлагодження (debug)
             * 3 інформація (info) - стандартно
             * 2 попередження (warn)
             * 1 помилки (error) -->
        <loglevel>3</loglevel>

        <!-- Максимальний розмір журналу -->
        <logsize>10000</logsize>

        <!-- Якщо увімкнено (1), тоді по досягненню logsize
             файл журналу буде переміщено до [error|access|playlist].log.DATESTAMP,
             в іншому випадку - [error|access|playlist].log.old. Стандартно - вимкнено. -->
        <!-- <logarchive>1</logarchive> -->
    </logging>

    <!-- Параметри запуску лишаю стандартними -->
    <security>
        <chroot>0</chroot>
        <!--
        <changeowner>
            <user>nobody</user>
            <group>nogroup</group>
        </changeowner>
        -->
    </security>
</icecast>
```
* не забудьте вказати актуальне значення PASSWORD
* після внесення змін, необхідно перезапустити сервіс `systemctl restart icecast2`

### Фаєрвол

У прикладі вище, використовується IPv6 маска хостів "::" (у вас це може бути IPv4 типу "0.0.0.0") для доступу без Веб-проксі. Тому відкриваємо й відповідний порт:

``` bash
ufw allow 8000/tcp
```
* приклад відкриття порту 8000 для IPv4 + IPv6 (TCP)

Якщо ви плануєте проксуватись через 80/443 порти Nginx (у вас там має бути вже відкритим 80/tcp / 433/tcp відповідно), або у вас локальний хост на кшталт "::1" чи "127.0.0.1" (стандартно) тоді відкривати не потрібно.

### Веб-проксі через Nginx

Хоча сам не користуюсь такою комбінацією, але додам нотатку з налаштування проксі для віртуального хосту Nginx, на прикладі конфігурації бекенд-сервера "127.0.0.1:8000"

``` default
server {
     # розкоментуйте, якщо використовуєте IPv4
     # listen IPv4:80

     listen [IPv6]:80;

     server_name IPv4 IPv6 DOMAIN_1 DOMAIN_2;

     location / {
          proxy_pass http://127.0.0.1:8000;

          # розкоментуйте значення якщо делегуєте журналювання серверу icecast
          # proxy_set_header X-Real-IP $remote_addr;
          # proxy_set_header X-Forwarded-Proto $scheme;
     }
}
```
* IPv4 і IPv6 - публічні адреси вашого Веб-сервера (Nginx)
* DOMAIN_1 і DOMAIN_2 - домени/субдомени/аліаси через пробіл, якщо використовуються
* не забуваємо відкрити порт 80 (і/або 443 якщо використовується)

### SSL/TLS

В принципі, можна використовувати модні "захищені" з'єднання, але для стрімінгу я бачу його використання не доречним з точки зору енергозаощадження і здорового глузду в плані шифрування потоків з публічними даними. Дивіться самі, особисто я по причині вище завжди прослуховую радіо-станції по HTTP, якщо така опція надається сервером.

## Ezstream

Коли "станцію" Icecast налаштовано, нам потрібно надіслати їй дані для трансляції. Такі дані можуть бути голосовими / потоковими або зберігатись у бінарних файлах. Оскільки мені потрібен останній варіант, з ним є одна не очевидна на перший погляд проблема: усі вони мають різні формати (`ogg`, `flac`, `mp3`, тощо) а також різні бітрейти та інші характеристики (в яких не торопаю). Оскільки вихідний стрім для слухача завжди має один формат, нам потрібно якимось чином відформатувати нашу колекцію до уніфікованого потоку: для цієї задачі й потрібен такий софт як Ezstream.

На відміну від популярної комбінації Icecast2 + Ices2 (https://icecast.org/ices/), Ezstream (https://icecast.org/ezstream/) відрізняється тим, що підтримує більше медіа-форматів з коробки та є простішим в налаштуванні (для поточних завдань)

### Встановлення

``` bash
apt install ezstream
```

### Налаштування

Створімо окремого користувача з домашньою текою, де зберігатимуться налаштування та аудіо-файли:

``` bash
useradd -m ezstream
```

### Створення списку ротації

Насамперед, варто зауважити, що якщо це ротація великої колекції по колу, то медіа файли бажано звести до єдиного формату. Якщо цього не зробити то Ezstream буде конвертувати файли на льоту, що потребуватиме додаткових ресурсів CPU. На цьому етапі я не робив того, використовуючи колекцію що складається з mp3 і стрім мій буде в цьому ж форматі. Єдине що, я сумніваюся щодо різного бітрейту та інших аудіо-характеристик, це буде окремою темою про конвертацію наприклад з `ffmpeg`, але майте на увазі цей нюанс.

Для початку, завантажимо потрібні аудіофайли до умовної теки /home/ezstream/my-stream/collection. Якщо планується декілька тематичних стрімів, буде зручно заздалегідь створити для них простір імен у вигляді підтек, наприклад:

* mkdir -p /home/ezstream/my-stream-1/collection
* mkdir -p /home/ezstream/my-stream-2/collection
* ...

Після того, як до колекції додано медіа-файли, згенеруємо для них мета-індекс для ротації в Ezstream. На виході це буде звичайний текстовий список шляхів до файлів по одному на рядок:

``` bash
find /home/ezstream/my-stream/collection -name *.mp3 > /home/ezstream/my-stream/playlist.txt
```
* у даному прикладі, всі мої файли у форматі mp3, тому пошук відбувається за відповідним суфіксом, інші розширення у цьому прикладі будуть проігноровані

Оскільки операції вище я проводив від рута (без попереднього логіну з `su ezstream`) варто виправити права Unix, адже наш сервіс systemd пускатиметься саме від користувача ezstream:

``` bash
chown ezstream:ezstream -R ~/ezstream
```

### Конфігурація Ezstream

Коли дані ротації готові, можна перейти безпосередньо до налаштувань Ezstream.

Щоб не переварювати зайві опції, копіюємо мінімальний конфіг в домашню теку користувача `ezstream`:

``` bash
cp /usr/share/doc/ezstream/examples/ezstream-minimal.xml /home/ezstream/my-stream/config.xml
```
* замініть "my-stream" на логічну назву проєкту та занотуйте шлях - він також буде потрібен для налаштування systemd

Якщо вам потрібні додаткові опції, дивимось доступні пресети з коментарями:

``` bash
ls /usr/share/doc/ezstream/examples/
```

Мій файл для конфігурації Icecast вище, містить деякі додаткові опції та зараз виглядає так:

``` /home/ezstream/my-stream/config.xml
<?xml version="1.0" encoding="UTF-8"?>
<ezstream>
  <servers>
    <server>
      <!-- Icecast крутиться на :: отже звернутись до нього можна через localhost/IPv6 -->
      <hostname>::1</hostname>

      <!-- Пароль від <source-password> в /etc/icecast2/icecast.xml -->
      <password>PASSWORD</password>
    </server>
  </servers>
  <streams>
    <stream>
      <!-- Вкажіть актуальні NAME, DESCRIPTION, GENRE -->
      <stream_name>NAME</stream_name>
      <stream_description>DESCRIPTION</stream_description>
      <stream_genre>GENRE</stream_genre>

      <!-- Шлях монтування: це URI для http://host:8000/my-stream.mp3 -->
      <mountpoint>/my-stream.mp3</mountpoint>

      <!-- Стандартним є ogg, утім в моєму випадку це mp3
           щоб уникнути зайвої конвертації "на льоту" -->
      <format>mp3</format>
    </stream>
  </streams>
  <intakes>
    <intake>
      <!-- Шлях до згенерованого вище списку файлів для ротації -->
      <filename>/home/ezstream/my-stream/playlist.txt</filename>

      <!-- Змішування треків у моєму випадку буде вимкнено -->
      <shuffle>No</shuffle>

      <!-- Також, я хочу щоб треки грали з початку після завершення списку -->
      <stream_once>No</stream_once>
    </intake>
  </intakes>
</ezstream>
```

Зауважу, що на кожен потік (стрім) потрібен окремий файл конфігурації / systemd процес, не додавайте масив <stream> в <streams> - це так не працює і якщо ви додасте декілька, потік просто видаватиме один з потоків. Тобто для кожного стріму - має бути окремий файл конфігурації і окремий конфіг systemd для нього, тому вище ми і створили неймспейс "/home/ezstream/my-stream"

### systemd

Останнє, що потрібно зробити - це створити сервіс systemd для налаштованого потоку Ezstream. Запускатиметься він від створеного вище користувача ezstream та вище створеної конфігурації "/home/ezstream/my-stream/config.xml"

``` bash
nano /etc/systemd/system/ezstream-my-stream.service
```
* назвати файл можна як завгодно, головне щоб ви його потім впізнали та відрізнили від інших сервісів / потоків Ezstream

``` /etc/systemd/system/ezstream-my-stream.service
[Unit]
After=network.target

[Service]
Type=simple

# актуальні користувач/група
User=ezstream
Group=ezstream

# я додав затримку, оскільки потік не зможе стартувати,
# якщо процес icecast того зробити не встиг (наприклад, при старті системи)
ExecStartPre=/bin/sleep 5s

# вказуємо коректний шлях до конфігурації
ExecStart=/usr/bin/ezstream -c /home/ezstream/my-stream/config.xml

# вказуємо актуальні шляхи до майбутніх журналів
StandardOutput=file:/home/ezstream/my-stream/mainstream-debug.log
StandardError=file:/home/ezstream/my-stream/mainstream-error.log

# можна розкоментувати, але тоді ви можете не помітити збій окрім як читаючи логи
# Restart=on-failure

[Install]
WantedBy=multi-user.target
```

Зберігаємось (Ctrl+X + Y) і виконуємо один раз в послідовності:

1. `systemctl enable ezstream-my-stream` - додаємо автозапуск стріма при старті системи
2. `systemctl start ezstream-my-stream` - стартуємо стрім
3. `systemctl status ezstream-my-stream` - перевіряємо статус
* `systemctl stop ezstream-my-stream` - зупинити стрім (опціонально)

Ось власне і все, перезавантажувати процес Icecast не потрібно, конкретний потік Ezstream буде змонтовано та відмонтовано автоматично під час запуску / зупинки його процесу.

## Тестування

Тепер можна в браузері перейти за адресою вашого сервера Icecast: на прикладі конфігурації вище, це http://[IP]:8000

Якщо у вас активні процеси Ezstream, вони мають відображатися в списку на головній сторінці. До кожного потоку, з права є кнопки експорту в форматах M3U і XSPF - якщо на них клікнути вони або скачаються або відкриються в плеєрі, що підтримує стрімінг. В себе на Fedora, я відкриваю .m3u як текстовий файл та виймаю з нього URL, після чого копіюю в розділ "Radio" програвача "Rhythmbox" :)

## Посилання

=> https://devzone.org.ua/post/veb-radio-v-linux-vstanovlennia-servera-icecast-ta-bazove-nalashtuvannia-rotatsiyi-z-ezstream Веб-версія цього матеріалу з коментарями на DevZone