github savonet/liquidsoap v2.5.0-rc1
Liquidsoap 2.5.0-rc1

pre-releaseone hour ago

This is the first release candidate for the 2.5.x release cycle!

TODO: more details!

2.5.0 (unreleased)

New:

  • Added getter(t) type annotation syntax for getter types (#5078).
  • Added atomic(f), which runs f without letting any other atomic section run at the same time. Script code now runs on several cores at once, so a group of changes that must not be observed half-done — writing several references together, or checking a flag and setting it — needs to say so.
  • Added r.exchange(v), which sets a reference and returns the value it replaced in one
    indivisible step. Now that script code runs on several cores at once, a flag guarding
    work that must happen only once cannot be read and set separately: two threads both see
    it unset and both do the work.
  • Added source.content operator returning an associative list of frame field names to their content format, track.format returning the content format of a single track, and format.description returning a typed record description of a content format.
  • Renamed the internal video content type from canvas to yuv420p, which better reflects the
    actual content; the content itself is still organized as a canvas of yuv420p layers. Type
    annotations such as source(video=canvas) must be updated to source(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 setting settings.video.detect_dimensions
    to false or by explicitly setting the video dimensions.
  • Added support for XML .nfo sidecar 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). It writes icecast's
    access, playlist and error logs, reports listener sessions through callbacks, serves an admin listener
    page, and hashes listener IPs by default (#5408). Its configuration file is reloaded on
    SIGHUP or reload() without disconnecting sources or listeners (#5418).
  • Added on_signal, which runs a function when the process receives a signal such as
    SIGHUP or SIGUSR2, and returns a release method (#5418).
  • 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
    crossfade or stretch may 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
    release method taking the callback back, so a function registering on a source it is given
    can release what it registered instead of keeping it alive for as long as that source lives.
    Existing scripts are unaffected, the returned value being 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 single release. This is what switch and
    cross use, 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 unifying their content types, while nothing can be assumed about what a source(_)
    streams.
  • Added domain to thread.run, pinning the function and every rerun to the
    scheduler worker on that domain. An object is only collected by a GC on the
    domain that allocated it, which makes this the way to ask for one.
  • http.transport.tls and http.transport.ssl read their certificate and key again once a
    day, so a renewed certificate applies without a restart or dropping connections. The
    reload_on argument changes when, reload() applies it at once, and the certificate and key
    arguments now accept getters (#5418).
  • Added Fedora packages for the current Fedora release, on x86_64 and aarch64, and a
    matching dnf repository: curl -fsSL https://repo.liquidsoap.info/setup.sh | sudo sh
    now configures it on Fedora as it does apt and apk elsewhere.

Changed:

  • Liquidsoap now requires OCaml 5.5 to build.

  • Removed the deprecated input.udp and output.udp operators. Use input.srt/output.srt
    for low-latency network streaming.

  • ffmpeg log messages now go through liquidsoap's logger by default, with the same timestamps, log file and levels as the rest of the logs. Set settings.ffmpeg.log.capture := false to print them to standard error as before.

  • output.harbor's on_connect and on_disconnect callbacks now receive a listener record with
    its session duration and bytes sent, and a new listeners method lists connected listeners. The
    ip field no longer includes the client port (#5408).

  • Scheduled work — requests, harbor clients, script callbacks, thread.run handlers — is now fully concurrent,
    taking advantage of OCaml 5's core-based concurrency, so a busy instance keeps up with much more of it at once.
    Heavy work in a callback or a request resolution no longer stalls playback either.

  • Added settings.scheduler.legacy, a fail-safe for a script that concurrent execution breaks: tasks run on
    threads, one at a time, as they did before. It will be removed in a later version once the concurrent
    scheduler has settled.

  • Removed optional faad decoder, superseded by ffmpeg (#5444)

  • The prometheus.latency metrics end in _ratio instead of _seconds: their values are ratios of the frame duration.

  • Deprecated settings.scheduler.generic_queues, settings.scheduler.fast_queues and
    settings.scheduler.non_blocking_queues: the scheduler sizes itself from the number of cores and there is
    nothing left to tune. They now only configure the legacy scheduler, and setting them without it logs a
    warning. settings.scheduler.blocking_tasks replaces them, limiting how many slow tasks — request
    resolutions, thread.run handlers, last.fm submissions — may run at once. It defaults to one per
    domain and never fewer than 8, so a machine with few cores keeps room to run several at once. Raising it pays off
    when those tasks truly wait. A task that uses a core instead of waiting on one, such as probing a
    file for its decoder, only takes cores the streaming threads need.

  • Clocks run as scheduler tasks rather than each on a thread of its own. A clock that is ahead
    of real time parks and is resumed by the scheduler's timer, so a machine running many outputs
    has as many busy threads as cores instead of one per clock competing for them. A clock
    catching up yields its domain to the others after each burst of frames. Ticks run directly on
    a scheduler domain, one at a time, ahead of request resolution.
    A clock driving JACK input or output keeps a thread of its own, since it rests by waiting on the
    JACK server. settings.clock.task := false restores a thread per clock everywhere.

  • When the scheduler is busy, quick work is served before slow work: the server, then request resolutions, then
    long tasks such as last.fm submissions. The order used to be arbitrary and often favoured the slow ones.

  • Bindings written without let accept the same targets as let — destructuring patterns, field paths and type
    annotations — so (x, y) = (1, 2), r.field = 1 and (n : int) = 2 are all valid, and an invalid left-hand side
    reports what is allowed instead of a bare syntax error. A leading binding inside the { … } function shorthand
    still requires let, since { x = 1 } is a record literal.

  • 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.ssl and http.transport.tls: key parameter 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
    defaulting automatically based on whether a source is file-based or live. See the
    new source composition page, and
    doc/content/migrating.md for the parameter-by-parameter mapping (#5074).

  • Switching now fades by default: a switch cutting into a playing track fades the
    leaving source out over up to settings.source.composition.max_fade seconds (default
    1.) when it carries only PCM audio, handoffs happening at a track boundary being
    unchanged since nothing was interrupted. Use source.composition.legacy_on_select to
    restore the previous behavior.

  • Deprecated fallback.skip. A source with track_sensitive set to false can be cut
    into mid-track and the file composition profile skips a source it was left in the
    middle of, so fallback([main, fallback_source.{track_sensitive = getter(false)}])
    now does natively what the operator polled for on every frame.

  • Simplified the cross/crossfade implementation: start_duration and end_duration are
    replaced by a single duration parameter, the metadata overrides liq_cross_start_duration
    and liq_cross_end_duration by liq_cross_duration, and the start_duration() and
    end_duration() methods by cross_duration(). Autocue-specific code and the
    assume_autocue setting are removed (#4893).

  • Add metadata from all sources/tracks in add operators (#4892).

  • Make sure script fails on on_close errors in output.file to prevent
    fatal errors from being ignored. Use reopen_on_error to 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_duration alone.

  • 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
    past settings.ffmpeg.filter_max_buffer.

  • xml-light and miniyaml are now required dependencies of liquidsoap-lang, making XML and
    YAML support always available. YAML integers parse as int instead of float (#5233).

  • YAML parsing and rendering now go through miniyaml, which covers the subset of YAML usually
    hand-written in configuration files. Block scalars (| and >), anchors, aliases, tags and
    multi-document streams are now rejected with an explicit error instead of being parsed, and
    yaml.stringify lost its scalar_style and layout_style arguments: it always renders in
    block style with plain scalars, quoting only where needed.

  • FFmpeg encoders and decoders now pick their thread count automatically, as the ffmpeg
    command-line tools do, instead of running on a single thread. Transcoding no longer
    bottlenecks on one core: an %ffmpeg output using libx265 and a 4K input is about 2.5
    times faster. Pass threads=1 to an %ffmpeg encoder to get the previous behavior back
    (#5014).

  • Video scaling can be split across cores through settings.ffmpeg.scaling_threads, as
    ffmpeg does through its filter graphs. It defaults to 1, scaling on the calling thread:
    splitting a frame costs a fan-out and a join every frame, which a stream held to real time
    pays continuously and, on our measurements, does not earn back. 0 uses one thread per
    core (#5014).

  • Removed daemon mode: the -d/--daemon command-line option, the settings.init.daemon
    settings and the pidfile they wrote. Detaching from the terminal meant forking, which is
    unsafe now that liquidsoap runs on several cores. Use a service manager such as systemd
    or launchd to run liquidsoap in the background; both handle pidfiles, log redirection and
    privilege dropping.

Fixed:

  • Active inputs such as input.ffmpeg replaced through source.dynamic are now stopped and
    released. They used to keep their connection, decoder and threads alive for the lifetime of
    the script (#5389).

  • A transition that drops the incoming source no longer drops that track's announcement with
    it. cross had already consumed the metadata into the buffer it hands the transition, so
    the track played on, audible and unannounced, once the transition was over. It is now
    announced on the tail, where that audio starts, and only when the transition did not
    announce it itself (#5360).

  • cross no longer replays either track's metadata into the transition. The outgoing one was
    announced a second time by a transition keeping that source, and with an add-shaped
    transition both replays landed at the same frame position, so only one survived and the
    incoming track's own announcement was dropped. The incoming one is already inside the
    buffered data it was replayed into, so it only ever fired when there was nothing to replay,
    announcing an empty metadata. The transition still receives both as the metadata field of
    its arguments, so a transition that read a track's fade overrides off the stream must now
    read them from that argument, as cross.simple and cross.plot do (#5360).

  • metadata.deduplicate no longer compares across a track mark, which dropped the metadata of
    a track repeating the one before it, e.g. a single file on a loop (#5360).

  • time.zone.set now takes effect. Setting the time zone was silently ignored once anything had already
    read the local time, which in practice meant always.

  • Rendering a type no longer goes through Format.str_formatter, which is per-domain and shared with whatever else
    formats a value there: a type printed from a scheduler task came out empty.

  • Log entries written during shutdown are no longer dropped: the logging thread returned without a final flush.

  • Error messages naming a type no longer come out with the type missing, which could happen when the error was
    raised from a callback or a thread.run handler.

  • The last lines of the log are no longer lost on shutdown.

  • Callbacks a script registers on the sources switch and cross hand to on_select,
    on_leave and 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
    going unavailable for a passing reason, a buffer filling up or a queue waiting on a
    request, still tears the graph down, since avfilter cannot reopen a graph that has seen
    end of file, but the graph is now rebuilt when the input comes back, each generation
    announced as a new stream so its timestamps line up with the last (#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 through loudnorm produced 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. on No 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/cgroup does not work under
    cgroup v2, which made the check fire on plain docker run (#3406).

  • Fixed output.file and the other piped outputs ignoring the delay returned by
    reopen_on_error when the failure happened while opening the file: the output
    stays Idle in that case and was retried on every streaming cycle (#2437).

  • output.file reports Unix errors, e.g. Permission denied when creating the
    destination directory, as system errors, like it already did for Sys_error
    (#2437).

  • Fixed %mp3 segfaulting 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).

  • rotate and random name the switch they build internally after themselves,
    e.g. schedule_rotate, instead of leaving it with an anonymous id (#3851).

  • Fixed clock.create documenting and reporting sync modes, "CPU" and
    "unsynced", that it does not accept. The accepted values are "auto",
    "cpu", "none" and "passive".

  • Fixed file.watch losing 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, and the containing
    directory is watched instead. playlist with reload_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.query or %argsof expand
    to. _null is 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 --interactive or 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 %ffmpeg copy 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 %ffmpeg video streams being printed as %%video(...).

  • Fixed FFmpeg stream descriptions listing video and subtitle streams in reverse
    order.

  • Fixed delay getting stuck after its first track and dropping the following
    track's metadata (#5282).

  • Fixed sequence dropping 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 a cross/crossfade transition built with
    sequence keeping the previous track's metadata until the next track
    boundary (#5296).

  • Fixed output.file.hls terminating the process on transient filesystem
    errors. The output now supports a reopen_on_error callback (#5259).

  • Fixed output.file.hls accepting unwritable output, temporary and
    persist_at directories 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 stereotool really be active.. (#4882)

  • Fixed fMP4 HLS 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.in duration is set to 0. (#4887)

  • Fixed race condition when registering harbor endpoint and mounts (#5292)

  • Fixed crash with concurrent stop/start operations (#4849)

  • Fixed metadata leak from underlying ffmpeg ogg demuxer in ffmpeg stream
    decoder (#4848)

  • Make sure output.file does 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 override parameter to insert_metadata: when false, metadata the source
    provides itself takes precedence over the inserted one. The composition profiles use it
    so that replaying metadata on selection never overwrites a fresh track's own metadata.

Don't miss a new liquidsoap release

NewReleases is sending notifications on new releases.