This is the sixth bugfix release of Liquidsoap 2.4.x.
While most of our energy goes into 2.5.x, bugs keep being reported and fixed along the way, and the ones that apply to 2.4.x deserve to reach the people running it in production!
There's also a trend of issues and fixes reported by automated tools that is, in fact, quite helpful.
It really helps when a user can come with a problem, a full description and analysis and sometimes even a proposed solution. We are aware of the complexity of the current changes but it's a positive effect when people who just love radio and have no previous OCaml expertise can come up with solutions to their own problem that we can effectively implement..
It should be safe to upgrade from any 2.4.x version. As usual, we recommend trying it on a staging environment before rolling out to production.
Debian and Ubuntu packages now ship our own FFmpeg
Starting with this release, our debian and ubuntu packages are statically linked against our own build of the FFmpeg libraries, maintained at savonet/ffmpeg-static-build. The packages install on a stock system, without pulling the distribution's FFmpeg, and every one of them comes with the same recent FFmpeg and the same set of codecs, whatever the distribution provides. This includes fdk-aac encoding in %ffmpeg.
Until now, our debian packages depended on the FFmpeg libraries from deb-multimedia.org, a third-party repository replacing the distribution's own packages, which caused all sorts of binary compatibility issues. This is no longer needed: you can remove that repository from your system if liquidsoap was your only reason for having it.
Package repositories
We now publish apt and apk repositories at https://repo.liquidsoap.info, so that installing and upgrading liquidsoap on Debian, Ubuntu and Alpine goes through your package manager instead of downloading a package by hand. A single command configures the repository matching your system and asks which release you want:
curl -fsSL https://repo.liquidsoap.info/setup.sh | sudo shAfter that, apt-get install liquidsoap or apk add liquidsoap does the rest. You can also name the release up front, which is what you want in a Dockerfile:
curl -fsSL https://repo.liquidsoap.info/setup.sh | sudo sh -s -- --channel v2.4.6Each release has its own channel, and there is a rolling channel for each supported version, rebuilt on every commit and picked up by an ordinary apt-get upgrade or apk upgrade. Both the liquidsoap and liquidsoap-minimal packages are available.
Notable fixes
- Metadata and track callbacks after a crossfade:
crossandcrossfadedropped the metadata of a track repeating the previous one, such as a single file on a loop, and handlers registered after the operator only ever saw the first track. Metadata now comes through andon_trackfires on every crossing. A synchronous callback reading the frame of its own source, e.g.s.time()insides.on_metadata, no longer hangs either. Reported by @caturria (#5360, #5361). - Fades:
fade.outwithtrack_sensitive=truemuted the whole next track after a skip, and a pendingliq_fade_*_delayon a track with a0.fade duration amplified by infinity. Both are fixed. Reported by @NonstopGutesMarketing (#5514). - HLS segment drift: Segment boundaries slowly drifted away from
segment_duration, at a rate depending on the encoder's frame size, so variants using different codecs diverged from each other. Segments now stay on the segment grid. Reported by @rmens (#5319), who also found that an error raised whileoutput.file.hlsshuts down could escape the clock'son_errorhandler and kill the process (#5260). - Icecast and harbor:
output.icecastwithchunked=truenow reconnects after the server goes away, reported by @TheKigen (#5519).output.harborclients asking for ICY metadata receive it, with a patch from @uhthomas (#5279).input.harborcleans up its sockets. - SRT:
output.srtno longer gets stuck after a clock latency reset, with a patch from @gonzafirewall (#5309). - Playlists reloaded on file change:
file.watchlost track of a file that was replaced rather than written in place, which is how most programs save a file.playlistwithreload_mode="watch"now also waits for the writer to finish before reloading. Reported by @vitoyucepi (#3343). output.fileretries: The delay returned byreopen_on_erroris now honoured when the failure happens while opening the file, instead of retrying on every streaming cycle. Reported by @vitoyucepi (#2437).- Long-running scripts: Several sources of unbounded growth are gone: in the type unifier, with patches from @lisfox1 (#5257) and @charles-rc (#5336), and in buffers, reported by @juniorkain (#5287).
- Catchable errors: Errors raised inside a builtin, such as
file.lson an unreadable directory, are now runtime errors thattry ... catchcan handle instead of aborting the script. Reported by @rmens (#5454). - Running as
root: Liquidsoap now logs a warning instead of exiting. The container detection behind that check does not work under cgroup v2, which made it fire on a plaindocker run. Reported by @vitoyucepi (#3406), along with log messages lost when liquidsoap exits before streaming (#3375). - Audioscrobbler: The Last.fm endpoint is fixed, with a patch from @iibaranov-IG (#5404).
A big thank you to everyone who took the time to report these issues, send patches and test the fixes!
Here's the full changelog:
2.4.6 (2026-10-08)
Fixed:
- Fixed
fade.in/fade.outamplifying by infinity while aliq_fade_*_delaywas
pending on a track whose fade duration is0.. - Fixed
fade.outwithtrack_sensitive=truemuting the whole next track
after a skip (#5514) - Fixed
output.icecastwithchunked=truenever reconnecting after the
server went away (#5519) crossandcrossfadewith their defaultdeduplicate=trueno longer drop
the metadata of a track that repeats the one before it, e.g. a single file on
a loop, which silenced the handlers a script registers after the operator.
crossnow marks the outgoing track's metadata where it replays it into the
transition, so the repeat it is meant to remove is told apart from a fresh
announcement rather than guessed at by comparing values (#5360)metadata.deduplicateno longer compares across a track mark, for the same
reason (#5360)crossandcrossfadenow fireon_trackon every crossing. The default
transition mixes the two tracks and carries no track mark of its own, so
handlers registered after a crossfade only ever saw the first track (#5379)- Fixed a synchronous callback hanging or looping forever when it reads the
frame of the source it is registered on, e.g. callings.time()inside
s.on_metadata(synchronous=true, ...)(#5361) - Fixed
file.watchlosing track of a file that gets replaced rather than
written in place: an inotify watch follows the inode, so a writer doing the
usual write-to-temporary-then-rename silenced the watcher for good. The
containing directory is watched instead.playlistwithreload_mode="watch"
also waits for a burst of events to settle before reloading, so a writer that
truncates before writing no longer triggers two racing reloads and plays
tracks out of order (#3343) - Anything logged while evaluating the script, deprecation warnings in
particular, was silently dropped when liquidsoap exited before reaching the
streaming loop, e.g. onNo output defined, nothing to do.(#3375) - Liquidsoap no longer exits when started as
root: it now logs a warning
instead. Container detection through/proc/1/cgroupdoes not work under
cgroup v2, which made the check fire on plaindocker run(#3406) - Fixed
output.fileand the other piped outputs ignoring the delay returned by
reopen_on_errorwhen the failure happened while opening the file: the output
staysIdlein that case and retried on every streaming cycle (#2437) output.filereportsUnixerrors, e.g.Permission deniedwhen creating the
destination directory, assystemerrors, like it already did forSys_error
(#2437)rotateandrandomname the switch they build internally after themselves,
e.g.schedule_rotate, instead of leaving it with an anonymous id (#3851)- Fixed
clock.createdocumenting and reporting sync modes,"CPU"and
"unsynced", that it does not accept. The accepted values are"auto",
"cpu","none"and"passive" - Added Unifier path compression to avoid unbounded deref cost growth (#5257)
- Fixed unbounded buffer growth (#5287)
- Fixed
sequencedropping the first chunk of a source, along with its
metadata, when the source it follows leaves no room in the current frame.
This showed up as the track after across/crossfadetransition built with
sequencekeeping the previous track's metadata until the next track
boundary (#5296). - Fixed ICY metadata negotiation in
output.harbor: theIcy-MetaDatarequest
header was looked up before normalization, so clients never received
in-stream metadata (thanks to @uhthomas, #5279) - Fixed
srtrestart logic (#5309) - Fixed HLS segment boundaries drifting away from
segment_duration: a segment
closing on a stale split position re-anchored the next boundary on it instead
of the segment grid. The drift rate depends on the encoder's frame size, so
variants using different codecs, e.g. HE-AAC and AAC-LC, diverged from each
other without bound (#5319). - Fixed a clock's
on_errorhandler being defeated by the shutdown of the
source it just contained: putting the failed source to sleep runs its cleanup,
e.g.output.file.hlsclosing its current segment whenpersist_atis set,
and an error raised there escaped uncaught and killed the process. Shutdown
errors are logged instead (#5260) - Fixed
switchandsequencenot notifying their new sync source when
switching child, leaving the clock synced on the source they just left - Fixed the type unifier retaining collapsed variables forever, by merging
unified variables using union by rank (#5342) - Fixed Audioscrobbler base url (#5404, @iibaranov-IG)
- Errors raised by the OCaml code behind a builtin, such as
file.lson an
unreadable directory, are now runtime errors thattry ... catchcan handle,
instead of aborting the script (#5454) - Fixed
input.harborsocket cleanup (#5479)