The idea to interrupt the main broadcast of Mikulski_Radio with another stream from OBS has always been on the surface. At first I just didn’t know how to do it, and then, when I got more comfortable with the Liquidsoap toolkit, another problem arose. But about everything in order.
DISCLAIMER: I am not a programmer and not a linuxoid, but only a copy-paster-enthusiast who shares what he could figure out. Therefore, it is possible that knowledgeable experts may find some points or formulations incorrect or ridiculous. The submission of this material assumes that you have a basic level of knowledge of Liquidsoap, and also understand the principle of operation and configuration of the Nginx-Rtmp module. Anyway, the following articles and tutorials will help you figure it out: Your own Restream Server(Nginx RTMP) Creating a 24/7 stream(Liquidsoap) Creating a 24/7 stream part 2(Liquidsoap)
Input.Rtmp
For these purposes, Liquidsoap provides an input.rtmp
operator that can read rtmp addresses and identify them as an audio/video signal source. As far as I understand, by default this operator can also act as an rtmp server: that is, you can send a stream to the address that is written directly in the Liquidsoap script:
obs = input.rtmp("rtmp://your.domain.ip/stream")
But in this case, the rtmp source must be constantly active. If Liquidsoap does not detect it, the script will issue an error and crash. This moment is solved simply: an argument is added to the operator that disables the “server” mode:
obs = input.rtmp(listen=false, "rtmp://your.domain.ip/stream")
Well, the active server will be Nginx-rtmp module, where an application
is created with an address that will read Liquidsoap (called stream
in this example):
rtmp {
server {
listen 1935;
application stream {
live on;
record off;
}
}
}
In order for Liquidsoap to automatically switch the playlist to the stream from OBS when the latter becomes active, a fallback
operator is added:
videos = playlist("/path/to/mp4")
obs = input.rtmp(listen=false, "rtmp://your.domain.ip/stream")
output = fallback([obs, videos])
Launch Liquidsoap, start streaming to OBS at the right address and everything works! The playlist stops playing and switches to displaying OBS. However, we are not happy for long: after stopping OBS, Liquidsoap freezes.
There is a way out of the situation. But through a workaround. Switch
and interactive.harbor
operators will be used instead of the fallback
operator to switch sources manually via the built-in web interface:
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)])
This will not get rid of the freezes, but if you manually switch the OBS source back to the playlist, stop OBS (here Liquidsoap freezes), and then start OBS again, then Liquidsop will unfreezes and the playlist will continue to play. OBS can now be turned off without consequences. However, if you try to connect again, Liquidsoap will not be able to switch sources, displaying an error in the log. But the playlist playback will not be disrupted. So there is only a restart of the script as a whole.
At this point, I was stuck for a very long time. It was clear that for some reason rtmp does not close the connection, which leads to bugs. And my programming skills are not enough to write a function to forcibly break the connection. In general, I have monitored all the branches of Liquidsoap on Github on this topic, but everything turned out to be in vain.
The solution came much later and from an unexpected place: when I was dealing with Nginx-backup-rtmp scripts for the backup server, there in the Nginx config I saw that the play_restart on
argument was used for application. It sends a message to the “subscriber” of the broadcast every time the stream is published or removed from publication.
rtmp {
server {
listen 1935;
application stream {
live on;
record off;
play_restart on;
}
}
}
I edited the Nginx config, launched it – and everything worked! Now it was possible to turn on/off OBS at any time and switch sources painlessly.
It remains only to add metadata for the OBS source as a cherry. Again, not quite correct, but a working method based on this tutorial:
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)
Offtop #1
In the course of further experiments, I found either a Liquidsoap bug or another problem with my own configuration. If you add more than one input.rtmp
to the script with binding to fallback/switch
operators, then Liquidsoap plays a “safe” (black screen with silence) source at startup, and the page with interactive.harbor
is not loading. I tried three different ways and none worked when both with one source everything is fine. At the same time, there is no influence whether a separate application
is created in Nginx-rtmp, or the same one, but with different keys.
The first scenario:
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)])
The second scenario:
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])
The third scenario:
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])`
However, I did not issue a bug report, since it is easily managed by the same Nginx-Rtmp: you just need to create another application
that will redirect the second publication to the first application
, readable by 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;
}
}
}
In what cases it may be necessary? In those when the second stream is needed, in addition to the radio, you need to additionally stream to another set of sites. For example, the first stream is a podcast. We connect with it on the radio directly. The second is a music stream, which also aims at several more sites.
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;
}
}
}
Offtop #2
For streaming on Twitch, I have been using a pad in the form of Restream.io . Since streaming directly from my region to Twitch servers leads to a very unstable connection. However, now this is not necessary, because I can already use my VPS with Nginx-rtmp as a gasket – but my hands have not reached that yet. In general, the rtmp address is now registered in my config on the VPS Restream.io .
As you might know, in the interface Restream.io there are toggle switches for launching streams to the desired sites. And so, when all the toggle switches are turned off, then at the start of OBS – application stream_2
starts the broadcast on application stream
, ok. However, if the toggle switch is on Restream.io enabled for Twitch before OBS is launched, then the stream will be launched on Twitch, but will not be “pushed” locally to application stream
. What is the reason for this, it is completely unclear to me. Moreover, even if I initially split streams on my home PC, the result is still the same.
So far I haven’t been able to figure out what it’s all about and I’m at the stage of testing different options. Therefore, keep in mind that such seemingly logical schemes can lead to unexpected results.
Well, as soon as I figure it out, I will definitely supplement this post. So far, the idea is to first run OBS and then click the toggle switch in Restream.io . Which is tolerable, but inconvenient. So most likely, I’ll just stop using this service to stream on Twitch.
UPD: In general, after a number of attempts, I came to the conclusion that restream.io it has nothing to do with it. For some reason, ‘push rtmp://localhost’ does not work very predictably: in some cases everything is fine, in others the stream is not “pushed”, and in others Liquidsoap stops listening to the specified address and has to restart it.
Therefore, as an alternative, I decided to use the ‘exec_publish’ method to run ffmpeg, which relays the stream to the desired address.
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;
}
}
}
That is, the method is almost similar to how I wrote in this post: https://mikulski.rocks/backup-stream-for-24-7/
For script.sh it is important to grant execution rights (chmod u+x), and it contains the following:
#!/bin/sh
nohup ffmpeg -re -i "rtmp://localhost/stream2/key" -c copy -f flv "rtmp://localhost/stream/key"
Apparently, this has a minimal effect on the final delay, but it works very stably and the connection occurs every time. At the same time, the CPU load is also minimal, since ffmpeg does not have to recode anything, because a copy of the source parameters is used.