devzone.org.ua/post/proksuvannia-m3u8-zasobamy-icecast.md
2025-11-01 14:28:20 +02:00

8 KiB
Raw Blame History

Проксування потоку m3u8 засобами ffmpeg в Icecast

Я не маю значного досвіду з адміністрування стрімінгових сервісів, раніше на базі Icecast робив тільки локальну ротацію музичної колекції, але зацікавив проєкт довкола-айтішних стрімів eQtv. Цей матеріал - невеличка нотатка про налаштування проксі на прикладі сервісу eQtv українською мовою для локальних мереж.

Витягнути потік виявилось задачею не тривіальною, я її постійно відкладав але згодом таки знайшов в дебагах (ctrl+shift+i) наступні доріжки:

  • https://eqtv.live:8083/eqtvua/eqtvua480/chunks_dvr.m3u8 - схоже, що відповідає за відео
  • https://eqtv.live:8083/eqtvua/eqtvua_hd_ukr/chunks_dvr.m3u8 - відповідає за аудіо-ряд (їх 3)

ffmpeg

Вже звичний мені ezstream не вміє проксувати потоки з URL, тому віднайшов спосіб з ffmpeg:

ffmpeg -i "https://eqtv.live:8083/eqtvua/eqtvua_hd_ukr/chunks_dvr.m3u8?nimblesessionid=xxx" -c:a copy icecast://user:password@127.0.0.1:8000/eQtv.aac

У прикладі вище - потік передається в Icecast "як є" у форматі AAC, але такий формат мені не зовсім підходить, бо я хочу окрім оверлейних мереж Yggdrasil і Mycelium ще й стрімити в I2P з його "вузьким" каналом. Хоч конвертація вимагає додаткових ресурсів CPU, все ж вирішив її застосувати, звівши до поширеного формату MP3 з бітрейтом 32 kb/s:

ffmpeg -i "https://eqtv.live:8083/eqtvua/eqtvua_hd_ukr/chunks_dvr.m3u8?nimblesessionid=xxx" -b:a 32k icecast://user:password@127.0.0.1:8000/eQtv.mp3

Як видно на прикладах вище, я додав до URL джерела аргумент ?nimblesessionid=xxx - він потрібен для того, щоб ffmpeg не плодив нові сесії в процесі читання. Тут я ще не знаю, як довго протримається поточна сесія, але якщо будуть проблеми - напишу скрипт, що витягає актуальний її номер та доповню цей матеріал.

UPD. очікувано, сесія прожила менше доби, тому створив такий скрипт, будемо пускати його в ExecStart сервісу systemd, замість сирої команди ffmpeg:

#!/bin/bash

# завантажуємо актуальний файл m3u8, що містить активний номер сесії
# та витягаємо перше знайдене значення в змінну SESSION_ID
CHUNKS_DVR="/home/eqtv/chunks_dvr.m3u8"
wget -O $CHUNKS_DVR https://eqtv.live:8083/eqtvua/eqtvua_hd_ukr/chunks_dvr.m3u8
SESSION_ID=$(grep -oP '(?<=sessionid=)\d+' $CHUNKS_DVR -m 1)
if [ -z "$SESSION_ID" ]; then
  echo "SESSION_ID is empty. Exiting script."
  exit 1
fi

# запускаємо стрім з актуальним значенням SESSION_ID
ffmpeg -i "https://eqtv.live:8083/eqtvua/eqtvua_hd_ukr/chunks_dvr.m3u8?nimblesessionid=$SESSION_ID" -b:a 32k icecast://user:password@127.0.0.1:8000/eQtv.mp3
  • цей скрипт передбачає роботу з systemd: під час помилки процесу, буде виконано Restart=on-failure, таким чином ключ сесії буде актуалізовано

Icecast

Декілька слів про налаштування сервера Icecast. Раніше, для локальних колекцій, мета-інформація про стрім в мене обслуговувалась сервером ezstream. Тут його немає, тому я додав такий набір до конфігурації точки монтування Icecast:

<mount type="normal">
    <mount-name>/eQtv.mp3</mount-name>
    <username>user</username>
    <password>password</password>
    <stream-name>eQtv українською мовою (аудіо, 32 kb/s)</stream-name>
    <stream-description>eQtv — це проект eQualitie, неприбуткової організації, що розробляє технології для підвищення цифрової стійкості, особливо для спільнот, яким загрожують цензура, стеження, зміна клімату та мережева ізоляція.</stream-description>
    <stream-url>https://tv.equalitie.org/uk/live</stream-url>
</mount>
  • відповідно, user:password мають відповідати тим, що вказані в команді ffmpeg

Systemd

Команду ffmpeg я виконую від системного сервісу, створивши відповідного користувача:

#/etc/systemd/system/eqtv-mp3.service
[Unit]
# якщо Icecast локальний, можна додати icecast2.target поряд з network-online
# After=network-online.target icecast2.target
After=network-online.target

[Service]
Type=simple

User=eqtv
Group=eqtv

# Затримка потрібна у моєму випадку через залежність від Icecast
# ExecStartPre=/bin/sleep 5s

# ExecStart=/usr/bin/ffmpeg -i ...
# ExecStart=/bin/bash /path/to/script.sh
ExecStart=/path/to/script.sh

# Журнали я вимкнув, але можна продебажити наступним чином
StandardOutput=null
# file:///home/eqtv/eqtv-mp3-debug.log
StandardError=null
# file:///home/eqtv/eqtv-mp3-error.log

# Бажано увімкнути, якщо замість команди ffmpeg в ExecStart
# використовується скрипт оновлення номеру сесії
Restart=on-failure

[Install]
WantedBy=multi-user.target
  • в ExecStart треба вказати відповідний набір атрибутів ffmpeg або шлях до скрипта (вище)

Nginx

Як видно з прикладу, мій сервер Icecast крутиться на інтерфейсі 127.0.0.1 для локальних потреб (щоб не ганяти трафік через оверлей). Окрім локалхосту, в мене відбувається ретрансляція на різні мережі IPv6, тому для зручності я проксую клієнтський трафік через Nginx:

#/etc/nginx/sites-available/default
server {
    listen [202:68d0:f0d5:b88d:1d1a:555e:2f6b:3148]:8000;
    listen [505:6847:c778:61a1:5c6d:e802:d291:8191]:8000;
    listen xx.xx.xx.xx:8000;

    access_log /var/log/nginx/icecast.access.log;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_redirect off;
    }
}
  • на прикладі listen - проксі на Yggdrasil та Mycelium і одна на локальну мережу IPv4
  • proxy_pass - може бути відмінний локальний порт від вказаного у прикладах