Кулстори: Переключение Mikulski_Radio с плейлиста на стрим из OBS (liquidsoap | nginx-rtmp) – Mikulski
Наложение сайта

Кулстори: Переключение Mikulski_Radio с плейлиста на стрим из OBS (liquidsoap | nginx-rtmp)

Идея, чтобы прерывать основную трансляцию Mikulski_Radio другим стримом из OBS, всегда лежала на поверхности. Сперва я просто не знал как это сделать, а потом, когда уже получше освоился в инструментарии Liquidsoap, то возникла другая проблема. Но обо всем по порядку.

ДИСКЛЕЙМЕР:
Я не программист и не линуксоид, а лишь энтузиаст-копипастер, который делится тем, в чем смог разобраться. Поэтому, не исключено, что знающие специалисты некоторые моменты или формулировки могут счесть неправильными или нелепыми.

Подача данного материала предполагает, что вы обладаете базовым уровнем знаний Liquidsoap, а также понимаете принцип работы и настройки конфига Nginx-Rtmp-модуля.
Как бы то ни было, следующие статьи и туториалы помогут вам разобраться:
Свой Рестрим-Сервер (Nginx RTMP)
Создание 24/7 радио-стрима (Liquidsoap)
Создание 24/7 радио-стрима ч.2 (Liquidsoap)

Input.Rtmp

Для этих целей в Liquidsoap предусмотрен оператор input.rtmp, который умеет считывать rtmp-адреса и определять их как источник аудио/видео сигнала. Насколько я понял, то по умолчанию этот оператор может выступать и в качестве rtmp-сервера: то есть можно отправить стрим по адресу, который прописывается прямо в скрипте Liquidsoap:

obs = input.rtmp("rtmp://your.domain.ip/stream")

Но в таком случае, источник rtmp должен быть постоянно активным. Если Liquidsoap его не обнаружит, то скрипт будет выдавать ошибку и вылетать. Решается этот момент просто: к оператору добавляется аргумент, отключающий режим “сервера”:

obs = input.rtmp(listen=false, "rtmp://your.domain.ip/stream")

Ну а действующим сервером будет Nginx-rtmp-модуль, где создается application с адресом, который будет считывать Liquidsoap (под названием stream в данном примере):

rtmp {
    server {
        listen 1935;

        application stream {
            live on;
            record off;
        }
    }
}

Чтобы Liquidsoap автоматически переключал плейлист на стрим из OBS, когда последний становится активным – добавляется оператор fallback:

videos = playlist("/path/to/mp4")
obs = input.rtmp(listen=false, "rtmp://your.domain.ip/stream")

output = fallback([obs, videos])

Запускаем Liquidsoap, стартуем стрим в OBS по нужному адресу и все работает! Плейлист прекращает свое воспроизведение и переключается на отображение OBS. Правда, радуемся недолго: остановив OBS, Liquidsoap зависает.
Выход из ситуации есть. Но через костыль. Вместо оператора fallback будут использоваться операторы switch и interactive.harbor, чтобы переключать источники вручную через встроенный веб-интерфейс:

interactive.harbor(port=8000, uri="/interactive")

videos = playlist("/path/to/mp4")
obs = input.rtmp(listen=false, "rtmp://your.domain.ip/stream")
i = interactive.bool("obs", false)

output = switch([(i, obs), ({true}, videos)])

Это не избавит от зависания, но если вручную переключить источник OBS обратно на плейлист, остановить OBS (тут Liquidsoap зависнет), а потом снова запустить OBS, то Liquidsop отвиснет и плейлист продолжит свое воспроизведение. OBS теперь можно выключить без последствий. Однако, при повторной попытке подключиться – Liquidsoap не сможет переключить источники, отобразив в логе ошибку. Но при этом воспроизведение плейлиста не нарушится. Так что тут только рестарт скрипта целиком.

На этом моменте я и застрял на очень долгое время. Было понятным, что по какой-то причине rtmp не закрывает соединение, что и приводит к багам. А моих скиллов программирования недостаточно, чтобы написать функцию для принудительного разрыва связи. В общем, я промониторил все ветки Liquidsoap на Github по этой теме, но все оказалось тщетно.
Решение пришло намного позже и из неожиданного места: когда я разбирался со скриптами Nginx-backup-rtmp для резервного сервера, то там в конфиге Nginx я увидел, что для application используется аргумент play_restart on. Он отправляет сообщение “подписчику” трансляции каждый раз, когда стрим публикуется или снимается с публикации.

rtmp {
    server {
        listen 1935;

        application stream {
            live on;
            record off;
            play_restart on;
        }
    }
}

Я отредактировал конфиг Nginx, запустил – и все сработало! Теперь в любой момент можно было включать/выключать OBS и безболезненно переключать источники.

Осталось только в качестве вишенки добавить метаданные для источника OBS. Опять же, не совсем правильный, но рабочий способ на основе вот этого туториала:

def live_title(m) =
  # Grab the current title
  title = m["title"]

  # Return a new title metadata
  [("title","Live Right Now!")]
end

obs = metadata.map(live_title, obs)

Оффтоп #1

В процессе дальнейших экспериментов обнаружил либо баг Liquidsoap, либо очередную проблему собственной конфигурации. Если добавить в скрипт больше, чем один input.rtmp с привязкой к fallback/switch операторам, то Liquidsoap при запуске включает “безопасный”(черный экран с тишиной) источник, а страница с interactive.harbor не грузится. Я попробовал три разных способа и ни один не сработал, когда как с одним источником все в порядке. При этом нет влияния, создается ли в Nginx-rtmp отдельный application, или же один и тот же, но с разными ключами.

Первый сценарий:

obs_1 = input.rtmp(listen=false, "rtmp://localhost/stream/key")
r1 = interactive.bool("rtmp1", false)
output = switch(track_sensitive=true, [(r1, obs_1), ({true}, videos)])

obs_2 = input.rtmp(listen=false, "rtmp://localhost/stream_2/key") 
r2 = interactive.bool("rtmp2", false)
output = switch(track_sensitive=true, [(r2, obs_2), ({true}, videos)])

Второй сценарий:

obs_1 = input.rtmp(listen=false, "rtmp://localhost/stream/key")
output = fallback(track_sensitive=true, [obs_1, videos])

obs_2 = input.rtmp(listen=false, "rtmp://localhost/stream_2/key") 
output = fallback(track_sensitive=true, [obs_2, videos])

Третий сценарий:

obs_1 = input.rtmp(listen=false, "rtmp://localhost/stream/key")
obs_2 = input.rtmp(listen=false, "rtmp://localhost/stream_2/key") 
output = fallback(track_sensitive=true, [obs_1, obs_2, videos])`

Впрочем, я не стал оформлять баг-репорт, так как это легко обходится силами все того же Nginx-Rtmp: просто надо создать еще один application, который будет рестримить вторую публикацию на первый application, читаемый Liquidsoap.

    server {
        listen 1935;

        application stream {
            live on;
            record off;
            play_restart on;
        }
        application stream_2 {
            live on;
            record off;
            play_restart on;
            push rtmp://localhost/stream;
        }
    }
}

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

    server {
        listen 1935;

        application stream {
            live on;
            record off;
            play_restart on;
        }
        application stream_2 {
            live on;
            record off;
            play_restart on;
            push rtmp://localhost/stream;
            push rtmp://youtube;
            push rtmp://trovo;
        }
    }
}

Оффтоп #2

Для стрима на Twitch я давно использую прокладку в виде Restream.io. Так как с моего региона стрим напрямую на сервера Twitch приводит к очень нестабильному соединению. Впрочем, теперь это необязательно, ведь я уже могу в качестве прокладки использовать свой VPS с Nginx-rtmp – но до этого не дошли еще руки. В общем, в моем конфиге на VPS сейчас прописан rtmp-адрес Restream.io.


Как известно, в интерфейсе Restream.io есть тумблеры для запуска стримов на нужные площадки. И вот, когда все тумблеры выключены, то при старте OBS – application stream_2 запускает трансляцию на application stream, ок. Однако, если тумблер на Restream.io включен для Twitch до запуска OBS, то стрим будет запущен на Twitch, но не “пропушится” локально до application stream. С чем это связано, мне совершенно непонятно. Причем, даже если я изначально разделяю потоки на своем домашнем ПК, то результат все равно такой же.
Пока что у меня еще не получилось выяснить с чем это все связано и нахожусь на стадии тестирования разных вариантов. Поэтому, имейте в виду, что подобные, казалось бы, логичные схемы могут привести к неожиданным результатам.
Ну а я, как разберусь с этим, обязательно дополню этот пост. Пока что идея заключается в том, чтобы сперва запускать OBS и потом щелкать тумблер в Restream.io. Что терпимо, но неудобно. Так что вероятнее всего, я просто перестану использовать этот сервис для стрима на Twitch.
UPD: В общем, после целого ряда попыток я пришел к выводу, что restream.io тут ни при чем. По какой-то причине push rtmp://localhost работает не очень предсказуемо: в каких-то случаях все нормально, в других поток не “пушится”, а в третьих liquidsoap перестает слушать указанный адрес и приходится его перезапускать.
Поэтому, в качестве альтернативы я решил пользоваться методом exec_publish для запуска ffmpeg, который и ретранслирует поток по нужному адресу.

  server {
        listen 1935;

        application stream {
            live on;
            record off;
            play_restart on;
        }
        application stream_2 {
            live on;
            record off;
            play_restart on;
            exec publish /path/to/script.sh;
            push rtmp://youtube;
            push rtmp://trovo;
        }
    }
}

То есть способ почти аналогичный тому, как я писал в этом посте: https://mikulski.rocks/ru/backup-strim-dlya-24-7/
Для script.sh важно выдать права на выполнение (chmod u+x), а содержится в нем следующее:

#!/bin/sh
nohup ffmpeg -re -i "rtmp://localhost/stream2/key" -c copy -f flv  "rtmp://localhost/stream/key"

Судя по всему, на конечную задержку это оказывает минимальное влияние, но при этом работает очень стабильно и подключение происходит каждый раз. При этом нагрузка на ЦПУ также минимальна, так как ffmpeg не приходится ничего перекодировать, т.к. используется копия параметров источника.

0 комментариев
Межтекстовые Отзывы
Посмотреть все комментарии