Skip to content

v0.10.0

Choose a tag to compare

@a-wing a-wing released this 27 Sep 05:56

Breaking Changes

  • liveion configured streams are now provisioned (always registered). Every [stream.<name>] config entry is registered at startup and listed in GET /api/streams and the Dashboard even while idle, is exempt from the orphan reaper and auto_delete_whip/auto_delete_whep timeouts, and can no longer be created or deleted through the admin API — POST / DELETE /api/streams/{stream} on a provisioned stream now return 409 Conflict. StreamCreated for these streams fires once at startup (so [hooks] scripts and recorder.auto_streams matching them trigger earlier than before); no StreamDeleted is ever emitted for them, except paired with an immediate StreamCreated when an internal teardown (RTSP re-ANNOUNCE, publisher-leave cascade) resets the stream to standby — that pair carries the new reset delete reason (LIVE777_REASON=reset for hooks) instead of api-deleted.
  • Removed webhook support from liveion. The [webhook] configuration section and the webhooks list are no longer recognized. Existing configurations that still contain a [webhook] section will have that section silently ignored. Remove it before upgrading to keep configs tidy.
  • WHIP/WHEP session IDs are now UUIDs (previously an opaque hash of an internal pointer). Session IDs remain opaque strings for API clients, but anything pattern-matching the old 32-hex format must be updated.
    • Webhook-style push notifications are replaced by Server-Sent Events (GET /api/sse/streams) and the net4mqtt xdata channel, both of which push full stream-state snapshots when the state changes.
    • The /api/sse/events endpoint has been removed. Use /api/sse/streams instead.
  • The liveion configuration file layout was restructured. Stream configuration moved from [[stream.sources]] tables carrying a stream_id field to named [stream.<name>] tables with [[stream.<name>.sources]] beneath them, and the [channel.streams.<name>] section (DataChannel ↔ UDP forwarding via a udp://…?host=…&port=… URL) was replaced by [stream.<name>.channel] with plain listen / target fields. Old-format config files must be migrated by hand.
  • The [sdp] codec blacklist was replaced by per-media whitelists (#398): disable_codecs is gone — use video_codecs / audio_codecs (empty = no restriction). Codec-associated payloads such as RTX now follow their fmtp apt= base codec instead of being left behind when the base codec is filtered out.
  • rtsp-listen:// was removed from the livetwo-based tools (#379): whipinto / whepfrom no longer embed an RTSP server, and their default input/output URLs changed from rtsp-listen:// to rtp://. RTSP serving moved into live777 itself (the new rtsp feature, below).
  • Removed the experimental livecam application and the livevod playback helper. Both binaries shipped with 0.9.x and are no longer built or packaged.

Added

  • liveion WHIP push target (new target-whip feature): a configured stream target url = "whip://[token@]host:port/whip/<stream>" pushes the stream to a downstream WHIP endpoint — the declarative, static form of cascade-push (POST /api/cascade/{stream} with target_url). The push is media-driven: one supervisor task per target establishes the cascade-push session when the stream gains a publisher (PublishStarted, real WHIP or a source's virtual one) and tears it down when the publisher goes away, so the push always negotiates against the current publisher's codecs and downstream nodes see ordinary publisher attach/detach cycles (their auto_delete_*/on-demand strategies keep working). Failed pushes and lost sessions are retried with exponential backoff (5 s doubling, 60 s cap), event-bus lag is reconciled against the manager, and a target on an on_demand stream acts as standing demand: its sources are (re)started whenever the stream has neither a publisher nor a push session, paced by the same backoff, so the relay recovers on its own once an unreachable downstream is back.
  • liveion WHEP pull source (new source-whep feature, included in source-all): a configured stream source url = "whep://[token@]host:port/whep/<stream>" pulls media from an upstream WHEP endpoint — the declarative, static form of cascade-pull — and takes part in the full source lifecycle: on_demand start/stop, automatic reconnect, codec-readiness gating, and subscriber RTCP feedback (e.g. PLI keyframe requests) forwarded to the upstream publisher. The outgoing peer gathers ICE candidates with the server's own [[ice_servers]] config. livetwo exposes the shared livetwo::whep::forward_rtcp_to_peer helper used both here and by whepfrom's TCP output.
  • liveion on-demand sources: [stream.<name>] on_demand = true keeps the stream's configured sources (camera/encoder, RTSP pull, SDP) stopped until the first subscriber (WHEP, cascade push, or RTSP pull) arrives, and stops them again on_demand_close_after_ms after the last one leaves. The first subscriber waits up to on_demand_start_timeout_ms for the source to become ready so its SDP answer already contains the tracks; subscribers arriving while a start is in flight wait for it instead of receiving a track-less answer, and if the source is not ready in time the subscribe request fails (and cleans up) instead of hanging forever. The Dashboard shows standby/on-demand badges for these streams and config for other provisioned streams; api::response::Stream gained matching provisioned/onDemand fields.
  • liveion stream-lifecycle hooks: the new [hooks] section (global) and [stream.<name>.hooks] (per stream) run external scripts on stream lifecycle events — stream created/deleted (on_stream_created/on_stream_deleted) and publish started/stopped (on_publish_started/on_publish_stopped) — e.g. to power a capture device / hardware encoder on when media is actually wanted and off when the last consumer leaves. Publish hooks receive the publisher session id as LIVE777_SESSION (configured sources report virtual-source) and, for stop events, the reason (peer-closed/api-deleted/idle-timeout). Scripts receive <event> <stream> [reason] as argv and the same values as LIVE777_EVENT/LIVE777_STREAM/LIVE777_REASON; execution is a single FIFO queue (global hooks first, then per-stream, in configured order) with per-script timeout_ms and an on_error = "stop"|"continue" policy.
  • Unified ICE server configuration across the livetwo-based tools (whipinto, whepfrom, whepprobe, whipsynth, livewrk): all of them take a repeatable --ice-server <url>[,<username>[,<credential>]] flag that also supports TURN credentials, including whipinto whose ICE servers were previously hardcoded. The flag has no default value — the tools gather host candidates only unless one or more --ice-server flags are given (an empty string explicitly disables ICE servers), since WHIP/WHEP endpoints already advertise their own ICE servers via Link headers. The synth:// input URL takes a repeatable ice=<spec> parameter that overrides the CLI list for that input. Specs are parsed, validated, and converted through the shared iceserver crate (IceServer::from_str, to_rtc_ice_servers, default_rtc_ice_servers, DEFAULT_ICE_SERVER_URL), and the CLI flag itself is the shared iceserver::IceServerArgs clap struct.
  • liveion stream media statistics (#252): Stream and Session API objects gained a stats field (bytes/packets cumulative, bitrate in bits per second, re-sampled every 2 seconds; byte counts are RTP wire sizes and exclude NACK retransmissions). Stream-level totals (stats.publish for the inbound publisher side, stats.subscribe for the aggregate of all outbound subscribers) stay monotonic and exact across republishes and subscriber churn — each departing flow's un-sampled tail is folded into the totals on removal — and line up with the per-session counters between sampling ticks. Server-wide totals are exported as the Prometheus counter live777_rtp_bytes_total{direction="in|out"}. The Dashboard shows per-stream In/Out rates with cumulative bytes, an Out column in the clients dialog, and server-wide totals in the header; statsScope reports whether stats are single-node (node) or liveman's cluster-wide node-work sum (clusterNodeWork, where cascade hops count once per relay node).
  • liveion source-change hooks: the new on_source_changed hook (global [hooks] and per-stream [stream.<name>.hooks]) fires when a source's parameter set is changed through the admin tier API (POST /api/sources/{stream}/tier) — a tier is a named preset of source parameters, applying one re-provisions the source. The scripts run synchronously inside the apply, before the tier's capture+encoder re-provisioning — for hardware that must be reconfigured ahead of the pipeline rebuild (e.g. a camera sensor's mode gear on Rockchip-style V4L2 pipelines, where framerate is a sensor-mode property the capture node cannot change). A failing script aborts the apply when on_error = "stop", before the pipeline is touched (the API call fails with 500). When the apply does not reach the target state — aborted, or the pipeline rebuild failed and rolled back — the scripts run again with the source's current state, so hardware they switched is switched back (best effort; scripts must be idempotent). Scripts receive <stream> <tier> as argv and LIVE777_STREAM / LIVE777_SOURCE_TIER / LIVE777_SOURCE_WIDTH / LIVE777_SOURCE_HEIGHT / LIVE777_SOURCE_FPS / LIVE777_SOURCE_BITRATE as environment — the declared tier values on the pre-apply run, the pipeline's actual current values on the compensation run. Unlike the lifecycle hooks, source-change hooks do not go through the FIFO hook queue.
  • livehal native capture + hardware-encoder pipeline: a camera now streams in-process, with no ffmpeg helper process. New native-source infrastructure with capture backends (capture-libcamera, capture-v4l2) and encoder backends (encoder-v4l2-m2m, encoder-rkmpp, encoder-rdk), plus native-rpi / native-generic-v4l2 / native-rdk / native-rkmpp platform presets. Includes Rockchip RKMPP support (RK3588, RV1126B), Raspberry Pi DMA-BUF zero-copy capture-to-encode, native UYVY input, per-stage pipeline latency instrumentation, and v4l2-m2m GOP / bitrate-mode control (gop = 0 switches to on-request keyframes). The release workflow publishes prebuilt -rpi and -rkmpp tarballs, and per-platform cross-build images are published on ghcr.
  • liveion embedded RTSP server (new rtsp feature): a single port serves mixed push (ANNOUNCE) and pull (DESCRIBE) sessions with Basic-auth support, so IP cameras, FFmpeg and GStreamer can publish to — or play from — the server directly over RTSP.
  • RTCP-feedback-driven adaptive bitrate for native encoder sources (issue #409): enabled by default (adaptive = false opts out, adaptive = { min_bitrate = … } sets a custom floor). An AIMD controller retunes the running encoder from WHEP subscriber RTCP feedback on encoder backends with runtime bitrate support (rkmpp, v4l2-m2m).
  • Source quality tiers and manual bitrate switching: GET / POST /api/sources/{stream}/tier lists and applies named source presets — bitrate-only tiers retune the encoder in place, while tiers carrying width/height/fps rebuild the capture+encoder pipeline with rollback on failure (subscribers stay attached across the sub-second gap) — and GET /api/sources/{stream}/bitrate reports read-only encoder telemetry.
  • liveion WHIP/WHEP server sessions now run as an ICE Lite agent.
  • New tools: livewrk (WHIP/WHEP load testing with whip and whep subcommands, named after wrk), whipsynth (synthetic test-frame WHIP publisher) and whepprobe (WHEP stream prober with human/JSON output) — the latter two, plus whipinto's new synth://<vcodec> input, publish in-process generated test frames with no external encoder (rsmpeg feature) — as well as whepwright (Playwright-driven browser WHEP playback tester, whepwright feature) and datachannel_loadtest.
  • whepfrom gained DataChannel reception and a UDP output extension.
  • GET /api/info reports the server version and compiled-in feature set, and every binary's --version output now also prints the enabled features.
  • The web player stack migrated from SolidJS to Vue 3 and was split into the framework-agnostic player-core engine and the @binbat/whep-player-vue component library (WhepPlayer, StatsForNerds, and a standalone query-param-driven player page served as /tools/player.html in both admin apps).
  • WebUI additions: cascade management in the dashboard, a lowlatency query param for the standalone player and debugger, per-second traffic rates in the player's stats-for-nerds, frame-rate controls for the QR latency tool, and a mobile UI overhaul.
  • AV1 improvements: whipinto supports AV1 input at RTP payload sizes above 1200 bytes, and the rtc-rtp AV1 depacketizer was patched so OBS AV1 recordings produce playable files.
  • Size-optimized embedded builds via the opt-in release-size Cargo profile plus UPX packing recipes (just build-size / pack-size, cross variants, and the device shortcuts rpi-pack-size / rdk-pack-size / v4l2-pack-size).
  • livetwo WHEP clients (whepfrom, livewrk whep) log one-way delay derived from RTCP Sender Reports.

Changed

  • liveion now rejects a WHIP publish onto a stream whose configured source is actively feeding tracks (and refuses to start an on-demand source while a WHIP publisher is live) with 409 Conflict, instead of mixing both publishers' tracks into every subscriber.
  • liveion configured sources (always-on and on-demand alike) now emit PublishStarted/PublishStopped lifecycle events with the synthesized virtual-source session id when they start/stop, and metrics::PUBLISH counts them. Recording of on-demand streams matching recorder.auto_streams is driven by these publish events (record only while the source is active) instead of StreamCreated/StreamDeleted.
  • liveion stream lifecycle events are now typed (liveion::event::Event) and travel on a single manager-wide broadcast bus: StreamDeleted emission is centralized into one funnel so it always pairs with its metrics update, and every consumer (SSE, net4mqtt, recorder) tolerates broadcast lag by re-syncing instead of silently stopping.
  • liveion subscribe-side RTP write errors are now retried with a 3s bound while the peer may still be coming up, instead of being classified by matching webrtc crate error strings.
  • liveion URL-source (RTSP/WHEP) reconnects now back off exponentially (5 s doubling, capped at 60 s) instead of retrying on a fixed 5 s interval, and the reconnect wait responds to source shutdown immediately instead of sleeping it out.
  • liveion now exposes a single SSE endpoint /api/sse/streams that pushes a full snapshot of all streams whenever stream state changes.
  • net4mqtt xdata messages now carry the sender identity as part of the channel tuple (sender_id, key, payload). The receiver no longer needs to trust a user-supplied alias field inside the payload.
  • liveman consumes net4mqtt xdata streams messages and uses the message metadata as the node alias.
  • liveman static nodes now support a mode field ("poll" or "sse", default "poll"). poll nodes continue to be updated by periodic HTTP polling, while sse nodes subscribe to the upstream /api/sse/streams endpoint for stream-state snapshots. net4mqtt nodes are unaffected and still update through xdata.
  • liveion SSE /api/sse/streams and net4mqtt xdata streams now deduplicate snapshots: a new message is only emitted when the actual stream-state snapshot differs from the previous one.
  • liveion net4mqtt xdata streams payload and vdata online payload no longer contain a redundant alias field; the sender identity is provided by the MQTT topic/channel metadata.
  • liveion net4mqtt xdata channel is now bounded (capacity 64) with backpressure: outdated snapshots are dropped when the channel is full.
  • liveman SSE subscriber now uses a 10s connect timeout, no per-request read timeout, and 30s TCP keepalive to recover from hung connections without frequent idle reconnects.
  • liveman no longer polls /api/strategy for dynamically discovered net4mqtt nodes.
  • liveion SSE /api/sse/streams and the net4mqtt xdata streams channel now also push on the 2-second media-stats sampling tick, deduplicated on the exact serialized payload: reported bitrates refresh while media flows, a silent stream's rate decay to zero is pushed exactly once, and an idle server sends nothing. Previously net4mqtt nodes' stats only updated on lifecycle events (and stayed frozen in between), since the snapshot comparison ignored stats.
  • A second WHIP publish on an already-published stream now displaces the incumbent publisher (mediamtx-style override) instead of failing with 409 Conflict: the old session is torn down with PublishStopped / SessionStopReason::Replaced, and same-codec takeovers are seamless to subscribers. Streams opt out with strategy.override_publisher = false (global or per-stream); cascade-pull publishers are never displaced and always conflict.
  • The webrtc / rtc-* dependency stack was upgraded to the crates.io 0.20.1 release, dropping the previous git pin.
  • libs/auth JWT verification was replaced with a minimal ring-based HS256 implementation, dropping the jsonwebtoken dependency.

Fixed

  • Fixed liveion stream teardowns losing session cleanup: closing a forward only closed the peers and relied on the peer-state handler (whose weak reference to the forward fails once the forward is dropped) to remove the sessions, so teardowns could lose PublishStopped/SubscribeStopped lifecycle events and skip the cascade session DELETE to the downstream node, leaving a zombie publisher there until ICE timed out. close now removes sessions through the same cleanup path as explicit removals.
  • Fixed liveion event consumers (recorder, net4mqtt notifier, SSE handler) silently exiting their receive loops on broadcast-channel lag bursts, which could permanently stop recorder auto start/stop and state notifications.
  • Fixed WHEP subscribe session-registration errors being swallowed, which could return a successful answer to the client without a working session.
  • liveion now logs a warning when a peer connection enters the Disconnected state (previously a lifecycle blind spot until it escalated to Failed), and closes the peer if it is still disconnected after 5s so sessions are torn down instead of lingering as zombies. Subscribe-side RTP forwarding now waits out a transient Disconnected state instead of permanently stopping on the first write error during it.
  • Fixed liveion leaking the peer connection when WHIP/cascade publish or subscribe setup failed mid-handshake; the peer is now closed on every pre-registration failure path.
  • Fixed a channel-sender leak in net4mqtt when XDataConfig.receiver was not provided.
  • Changed MQTT subscribe/publish calls in net4mqtt to propagate errors instead of panicking on connection failures.
  • Fixed liveman storage update logic so that stale stream/session mappings for a node are cleared before applying a new snapshot.
  • Fixed task leaks in liveion net4mqtt reconnection logic.
  • Fixed GET /api/sources, GET /api/sources/{stream} and GET /api/sources/{stream}/state dropping the request (handler task panic, empty reply) whenever a source existed: source state moved from tokio::sync::RwLock::blocking_read — which panics inside an async runtime — to a plain std::sync::RwLock.
  • Fixed liveion failing to compile with only source-sdp (missing rtsp_codec module gate) or only source (missing livetwo dependency), and cleaned up the dead_code/unused warnings across feature combinations by narrowing cfg gates to the features that actually use each item; the unused PeerForward::first_video_codec helper was removed.
  • Fixed libwish WHIP/WHEP HTTP requests having no timeout: a peer that accepts the connection but never responds could wedge whepfrom/whipinto, cascade setup, and a configured source's reconnect loop or stop() forever. Requests now use a 5 s connect / 30 s overall timeout, and WHEP URL sources use at least a 35 s on-demand readiness wait so a subscribe held by a cold upstream still succeeds.
  • Fixed subscriber RTCP feedback (PLI/FIR keyframe requests) silently stopping for the rest of a source bridge's life after any feedback burst: the bridge's RTCP handler broke out of its loop on the first broadcast Lagged error; it now skips the lagged messages and keeps forwarding.
  • Source URLs with embedded credentials (rtsp://user:pass@…, whep://token@…) are now redacted in liveion log lines instead of being written out in full.
  • Fixed livetwo WHEP client peers hardcoding stun:stun.l.google.com:19302 for ICE gathering: livetwo::whep::setup_whep_peer now takes the ICE server list from its caller instead. The liveion WHEP pull source uses the server's own [[ice_servers]] config, and whepfrom, whepprobe and livewrk whep gained a repeatable --ice-server <url>[,<username>[,<credential>]] flag that also supports TURN credentials (an empty string disables ICE servers — same semantics as the WHIP side).
  • Fixed RTSP-source subscriber RTCP feedback (PLI keyframe requests) stopping after the first reconnect: the bridge's RTCP wrapper captured the channel of the connection active at bridge creation and died with it; it now resolves the current connection per message, matching the WHEP source.
  • Hardened WHEP source URL handling: a password in userinfo (whep://user:pass@…) is now rejected instead of silently dropped, userinfo is always stripped from the URL used for requests and log lines, and liveion config-validation errors no longer echo source URLs (credential leak into startup logs).
  • Fixed the WHEP source misrouting late-arriving media after a partial codec ready: when the answer SDP negotiated audio+video but one kind missed the readiness grace, the single-kind codec snapshot stays authoritative, and packets of the missing kind are now dropped instead of being pumped onto the other kind's bridge channel (e.g. late video onto the audio track).
  • Fixed the WHEP source not detecting an upstream codec change across reconnects: the attempt now fails when the reconnected session reports a different mime type, clock rate, or channel count for an existing kind, instead of forwarding undecodable media through the bridge's stale virtual tracks.
  • Fixed libwish auth header construction panicking on tokens containing header-invalid characters (e.g. control characters), which killed the caller's task silently: Client::get_auth_header_map/get_authorization_header_map now return Result, and the WHEP source validates its token at construction.
  • Fixed chained on-demand WHEP pulls failing at the libwish HTTP timeout: an upstream liveion may hold a subscribe until its on-demand WHEP source is ready (up to 35 s), exceeding the hardcoded 30 s request timeout and looping the pull forever. The timeout is now configurable per client (Client::with_timeout, default unchanged at 30 s), and the WHEP source uses 40 s so one chained hop works by default; deeper chains need their budgets aligned (documented).
  • The liveion WHEP pull source's outgoing peer now also binds the UDP sockets from [webrtc] ice_udp_addrs (previously only the LIVE777_WEBRTC_ICE_UDP_ADDRS env var was consulted), and no longer joins the upstream's WHEP control group with an unnecessary control DataChannel.
  • Fixed the WHEP source misclassifying expected media kinds when the answer SDP carries multiple m-lines of the same kind (any active m-line now counts, not last-wins), made source URL scheme matching case-insensitive everywhere config validation already accepts (WHEP://… passed validation but was rejected by the source factory), and stopped broadcasting duplicate source-state events on no-op transitions.
  • Fixed WHEP subscribers left permanently silent when a publisher restarted or switched codecs: subscribers stranded on dead publish tracks are now re-bound to the live ones, and the bound sender track is reused across codec switches so early subscribers unmute again (#406, #407, #428).
  • Fixed forwarded RTCP Sender Reports carrying the wrong SSRC and RTP timestamp base.
  • Fixed the SDP source's H265 fallback fmtp advertising an invalid HEVC profile-id, and corrected the H265 level-compatibility check comparing its parameters in the wrong order.
  • Fixed the RTSP client lacking Basic-auth support and not defaulting to port 554.
  • Fixed livetwo sending DTLS packets before the peer connection was established.
  • Fixed livehal encoder frame-lifecycle bugs — armed frames leaked in the non-DMA-BUF backends, and a V4L2 buffer requeue could race rkmpp reading the dmabuf — and hardened RKMPP error recovery and V4L2 frame integrity.

Full Changelog: v0.9.0...v0.10.0