Example of implementing a pop-up banner in Liquidsoap (+NodeJS) – Mikulski
Site Overlay

Example of implementing a pop-up banner in Liquidsoap (+NodeJS)

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 NodeJS
This example is relevant for Liquidsoap version 2.2.1.
Anyway, the following articles and tutorials will help you figure it out:
Creating a 24/7 stream(Liquidsoap)
Creating a 24/7 stream part 2(Liquidsoap)

Thanks to discussions on Github: https://github.com/savonet/liquidsoap/issues/3202 & https://github.com/savonet/liquidsoap/discussions/3402, finally, I realized how it is possible to “legally” change the already superimposed images/animations on the video source. Which ultimately led me to add an animated banner that pops up on a timer to my stream (thanks for idea to get_ked). But this algorithm is quite applicable for other cases, up to the creation of improvised “alert-widgets”, provided that there is a bot that catches events about donation / subscription / raid and so on. Or to change the overlay depending on the time of day. In general, this technique can find a lot of applications.

The idea is that an empty transparent png file is superimposed on the video source using the video.add_image operator (not literally an empty file, but still an alpha channel must be added to it, i.e. a transparent layer and dimensions), which is replaced by another banner file at a certain time interval. The animation plays the allotted time and then video.add_image switches back to transparent png.
As an animation, you can use gif.

To automate file switching and set intervals, I decided to use the simplest js script for nodejs. Firstly, because I don’t have enough understanding of how this can be implemented inside Liquidsoap (I stalled at the moment before adding the timeout of the function), and secondly, it seems to me that it’s easier to edit the trigger intervals without restarting Liquidsoap itself (if you don’t mess around with interactive.values).


The main video source will be a playlist with video files – videos. You will need an empty blank image – blank.png and an animation – banner.gif (let’s say, with a duration of 10 seconds), which will have a common variable banner_file – it is assigned as a parameter to the video.add_image operator.
For banner_file, the ref operator will be used (“creates a reference, i.e. a value that can be changed”). blank.png is set as the initial ref value so that when you start Liquidsoap, the banner does not hang out on the screen.

videos = mksafe(playlist("/path/to/mp4"))
banner_file = ref("/path/to/blank.png")
videos = video.add_image(file=banner_file, x=0, y=0, height=100, width=1280, videos)

Well, then, my established practice is already: file.watch to launch the function and appendFile 😀

videos = mksafe(playlist("/path/to/mp4"))
banner_file = ref("/path/to/blank.png")
videos = video.add_image(file=banner_file, x=0, y=0, height=100, width=1280, videos)

file.watch("/path/to/banner_on", {banner_file.set("/path/to/banner.gif")})
file.watch("/path/to/banner_off", {banner_file.set("/path/to/blank.png")})

If there are changes in the banner_on file, the path to banner is set in the banner_file.gif
If there are changes in the banner_off file, the path to blank is set in the banner_file.png


Now it remains to create a js script and run it:

const fs = require('fs');
const banner_on = '/path/to/banner_on';
const banner_off = '/path/to/banner_on';

function banner_on_func() {
//we add the "i" symbol to the end of the banner_on file, thereby enabling the banner display
fs.appendFile(banner_on_func, `i`, (err) => { 
    if (err) {
//the pause in the script execution is equal to the duration of the banner animation (10 seconds)
          setTimeout(banner_off_func, 10000); 
          function banner_off_func () {
//we add the "x" symbol to the end of the banner_off file, thereby turning off the banner display
          fs.appendFile(banner_off, `x`, (err) => {
            if (err) {
//banner display interval (every 10 minutes)
setInterval(banner_on_func, 60000 * 10);    

A couple of nuances

I was most worried about timings: how exactly the intervals of the function execution in the js script will coincide with the end of the animation. Surprisingly, everything works very clearly, only sometimes it happens that the animation is interrupted a little earlier. But, I assume that if the animation differs in the number of frames from the stream (for example, a 30fps stream is a 20 fps animation), then you will have to “try on” the timings to get into the right interval. But no one forbids adding an extra second to the end of the animation with full transparency. Then it will be easier to aim.
From the pleasant: while the path to the “dummy” is in video.add_image, then the animation with the banner can be changed to another file (but with the same name). I’m not sure that this will work in the long run and with frequent substitution, but if necessary, you can do this without resorting to restarting Liquidsoap.

Inline Feedbacks
View all comments