This release provides liquidsoap assets before they are published as a new versioned release.
You can use it to install the latest stable code before it is published and test/prepare your production environment for it.
Rolling releases can also be useful for us to quickly detect and report bugs before the final published release!
⚠️ Warning ⚠️
Assets in this release will be deleted. If you are looking for permanent links to release assets, please head over to https://github.com/savonet/liquidsoap-release-assets/releases
For more details about our release process, please checkout https://github.com/savonet/liquidsoap#release-details
2.5.0 (unreleased)
New:
- Added
getter(t)type annotation syntax for getter types (#TODO). - Added
source.contentoperator returning an associative list of frame field names to their content format,track.formatreturning the content format of a single track, andformat.descriptionreturning a typed record description of a content format. - Renamed internal video content type from
canvastoyuv420pto better reflect the actual content. The video content remains organized as a canvas (superposition ofyuv420players) internally. Type annotations such assource(video=canvas)must be updated tosource(video=yuv420p). - Added subtitles support as a dedicated content type. Includes native SRT decoding,
FFmpeg subtitle encoding/decoding (%subtitle), passthrough for bitmap subtitles
(%subtitle.copy), callbacks (on_subtitle), transformations (subtitles.map),
and dynamic insertion (subtitles.insert) (#4860, #4861, #4862). - Video dimensions (
video.frame.width/height) are now automatically detected
from the first decoded video file. This can be disabled by settingsettings.video.detect_dimensions
tofalseor by explicitly setting the video dimensions. - Added support for XML
.nfosidecar files as a metadata source (enable_nfo_metadata,
file.nfo.metadata) (#4910). - Added Icecast-compatible streaming server (
icecast.server) with support for source
authentication, mount points, relay, and per-listener encoding (#4915). - Added dedicated encoder mode to
output.harbor: mount points can now be served with
a single shared encoder instead of per-listener encoding (#5003). - Rewrote JACK I/O using native OCaml bindings, removing the dependency on the
bjack
C library (#5028). - Added
settings.clock.child.max_buffer, the amount of data an operator such as
crossfadeorstretchmay buffer from its child source. Operators sharing a child
source must consume it at converging rates; past this value, an error names the operator
that fell behind instead of letting the buffer grow without bound (#5267). - Source callbacks (
on_track,on_metadata,on_frame,on_connect, ...) now return a
releasemethod taking the callback back. A function registering on a source it is given
keeps the callback alive for as long as that source lives, so one that runs more than once
needs to release what it registered. Past five callbacks on the same source, a log message
says so. Existing scripts are unaffected: the returned value is still ignorable. See the
new source callbacks page. - Added
source.collect_callback_releases, which runs a function and gathers the callbacks
it registered on a given list of sources into a singlerelease. This is whatswitchand
crossuse, available to scripts calling their own functions more than once. - Clocks created by script code running after startup are now started by the code that
created them, through an effect handler scoped to that code, instead of a global registry
polled after every script application. - Added the
source(_)type: a source whose content is unknown. Any source can be used where
one is expected, so sources of different content can be held together, for instance in a
list, without their content types being unified into one. The converse is rejected: nothing
can be assumed about what asource(_)streams.sourceandsource(...)are unchanged
and still mean a source whose content is inferred.
Changed:
- Bindings written without
letaccept the same targets aslet: destructuring patterns, field paths and type
annotations.(x, y) = (1, 2),r.field = 1and(n : int) = 2are all valid. A leading binding inside the
{ … }function shorthand still requireslet, since{ x = 1 }is a record literal. An invalid left-hand side
reports what is allowed instead of a bare syntax error. - String interpolation accepts any expression:
"#{m["key"]}","#{ {a = 1}.a }"and nested interpolated strings
now work. Error positions inside#{ … }point at the offending code rather than at the start of the string. http.transport.sslandhttp.transport.tls:keyparameter is now optional in server mode when the certificate file also contains the private key.switch,fallback,rotate,random: replaced the parallel list parameters
(transitions,transition_length,override,track_sensitive,
replay_metadata,single,weights) with per-source composition methods
(composition_type,track_sensitive,replay_metadata,single,weight,
on_select,on_leave) registered on every source. Switching behavior now
defaults automatically based on whether a source is file-based or live. See the
new source composition page, and
doc/content/migrating.mdfor the parameter-by-parameter mapping (#5074).- Switching now fades by default. Previously a switch that cut into a playing
track did so abruptly; the leaving source is now faded out over up to
settings.source.composition.max_fadeseconds (default1.), when it carries
only PCM audio. Handoffs that happen at a track boundary are unchanged: nothing
was interrupted, so nothing is faded. Use
source.composition.legacy_on_selectto restore the previous behavior. - Deprecated
fallback.skip. A source withtrack_sensitiveset tofalsecan be cut
into mid-track and thefilecomposition profile skips a source it was left in the
middle of, sofallback([main, fallback_source.{track_sensitive = getter(false)}])
now does natively what the operator polled for on every frame. - Simplified
cross/crossfadeimplementation: replacedstart_durationandend_duration
with a single unifieddurationparameter. Removed autocue-specific code and
assume_autocuesetting. Metadata overridesliq_cross_start_durationand
liq_cross_end_durationreplaced byliq_cross_duration. Methodsstart_duration()
andend_duration()replaced bycross_duration()(#4893). - Add metadata from all sources/tracks in
addoperators (#4892). - Make sure script fails on
on_closeerrors inoutput.fileto prevent
fatal errors from being ignored. Usereopen_on_errorto ignore errors
from the callback. - Allow implicit casting of an integer as a float (#2887).
- Removed
settings.decoder.ffmpeg.max_interleave_delta. The setting was never read by
the decoder; stream interleaving is bounded by
settings.decoder.ffmpeg.max_interleave_durationalone. - Overhauled the FFmpeg filter graph implementation, fixing frame loss, dropped
track marks and a hang. A graph is now a single source whose outputs share one
buffer and one set of track marks, so a track boundary cuts all of them,id
on any output names the graph, and outputs consumed at diverging rates raise
pastsettings.ffmpeg.filter_max_buffer.
Fixed:
- Callbacks a script registers on the sources
switchandcrosshand toon_select,
on_leaveand transition functions are now released when the selection or the crossing
ends, instead of accumulating on those sources for as long as they live. - An FFmpeg filter graph is no longer finished off by an input that runs dry.
A source is unavailable for all sorts of passing reasons, abufferfilling
up or a queue waiting on a request, and liquidsoap has no way to tell those
from a stream that is over, so the graph treats every one of them as the end:
it flushes the filters, hands over the tail they were holding and tears the
graph down. What is new is that this is no longer final. avfilter cannot
reopen a graph that has seen end of file, so when the input comes back the
graph is built again from what the script described, and each generation is
announced as a new stream so its timestamps line up with the last. Graphs
holding a lookahead,loudnormin particular, went silent within a second of
starting behind abuffer(#3944). - An FFmpeg filter graph dropped the tail its filters were holding at end of
stream: the sink never asked its duration converter for what it had left. A
5.00s source throughloudnormproduced 2.08s, and now produces 4.98s. - Fixed the ffmpeg decoder leaving up to ~150ms of silence after a seek: the
packets between the seek point and the target were dropped without being
decoded, so codecs carrying state across packets, mp3 and its bit reservoir
in particular, started cold. This is what made a CUE playlist click between
tracks (#3971). - 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).- Fixed
%mp3segfaulting on musl-based systems such as Alpine: the lame
bindings held their 144KB output buffer on the stack, more than a musl thread
stack can take (#3936). 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". - 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). - 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). - The names liquidsoap's syntactic sugar expands to are no longer writable in a script: they now start with an
underscore followed by a digit, which the lexer does not accept as an identifier, so user code cannot shadow what
let eval,let json.parse,let xml.parse,let yaml.parse,let sqlite.row/sqlite.queryor%argsofexpand
to._nullis unchanged, being a real callable builtin. - Record fields are evaluated when the record is built, whether or not they are ever invoked. Previously a field of a
record that was invoked directly, as in{a = 1, b = f()}.a, was dropped along with its effects when running a
script normally, but not under--interactiveor in the standard library, and not if the record was bound to a name
first. - Inline
ffmpeg.encode.*operators report unrecognized codec options, as the
container encoder does. - Fixed
%ffmpegcopy encoder initializing the video stream twice and setting the
average frame rate on the one it discarded, so copied video carried no frame rate. - Fixed inline
ffmpeg.encode.*operators losing their codec options after the
first track: the encoder consumed them from the format's own table, which was
then empty when the encoder was rebuilt at the next track boundary. - Fixed
%ffmpegvideo streams being printed as%%video(...). - Fixed FFmpeg stream descriptions listing video and subtitle streams in reverse
order. - Fixed
delaygetting stuck after its first track and dropping the following
track's metadata (#5282). - 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 (#TODO). - Fixed
output.file.hlsterminating the process on transient filesystem
errors. The output now supports areopen_on_errorcallback (#5259). - Fixed
output.file.hlsaccepting unwritable output, temporary and
persist_atdirectories during initialization (#5261). - Fixed ID3 timed metadata missing from first mpegts HLS segment: metadata is
now registered before the segment is opened and before data is encoded (#5084). - Make active
stereotoolreally be active.. (#4882) - Fixed
fMP4HLS support for audio+video streams (#4841) - Fixed crossfade clocks inconsistencies leading to weird caching audio glitches (#4851)
- Fixed first frame of new track getting lost when crossfading with
fade.induration is set to0.(#4887) - Fixed race condition when registering harbor endpoint and mounts (#5292)
- Fixed crash with concurrent
stop/startoperations (#4849) - Fixed metadata leak from underlying ffmpeg
oggdemuxer inffmpegstream
decoder (#4848) - Make sure
output.filedoes not create files without data (#4899) - Fixed deadlock when a GC finalizer logged from inside another log call: the log
queue no longer allocates while holding its mutex. - Added an
overrideparameter toinsert_metadata. Whenfalse, metadata the
source provides itself takes precedence over the inserted one. Used by the
composition profiles so that replaying metadata on selection never overwrites a
fresh track's own metadata.