Repository navigation
v0.10.0
Breaking Changes
liveionconfigured streams are now provisioned (always registered). Every[stream.<name>]config entry is registered at startup and listed inGET /api/streamsand the Dashboard even while idle, is exempt from the orphan reaper andauto_delete_whip/auto_delete_wheptimeouts, and can no longer be created or deleted through the admin API —POST/DELETE /api/streams/{stream}on a provisioned stream now return409 Conflict.StreamCreatedfor these streams fires once at startup (so[hooks]scripts andrecorder.auto_streamsmatching them trigger earlier than before); noStreamDeletedis ever emitted for them, except paired with an immediateStreamCreatedwhen an internal teardown (RTSP re-ANNOUNCE, publisher-leave cascade) resets the stream to standby — that pair carries the newresetdelete reason (LIVE777_REASON=resetfor hooks) instead ofapi-deleted.- Removed webhook support from
liveion. The[webhook]configuration section and thewebhookslist 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 thenet4mqttxdata channel, both of which push full stream-state snapshots when the state changes. - The
/api/sse/eventsendpoint has been removed. Use/api/sse/streamsinstead.
- Webhook-style push notifications are replaced by Server-Sent Events (
- The
liveionconfiguration file layout was restructured. Stream configuration moved from[[stream.sources]]tables carrying astream_idfield to named[stream.<name>]tables with[[stream.<name>.sources]]beneath them, and the[channel.streams.<name>]section (DataChannel ↔ UDP forwarding via audp://…?host=…&port=…URL) was replaced by[stream.<name>.channel]with plainlisten/targetfields. Old-format config files must be migrated by hand. - The
[sdp]codec blacklist was replaced by per-media whitelists (#398):disable_codecsis gone — usevideo_codecs/audio_codecs(empty = no restriction). Codec-associated payloads such as RTX now follow their fmtpapt=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/whepfromno longer embed an RTSP server, and their default input/output URLs changed fromrtsp-listen://tortp://. RTSP serving moved intolive777itself (the newrtspfeature, below).- Removed the experimental
livecamapplication and thelivevodplayback helper. Both binaries shipped with 0.9.x and are no longer built or packaged.
Added
liveionWHIP push target (newtarget-whipfeature): a configured stream targeturl = "whip://[token@]host:port/whip/<stream>"pushes the stream to a downstream WHIP endpoint — the declarative, static form ofcascade-push(POST /api/cascade/{stream}withtarget_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 (theirauto_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 anon_demandstream 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.liveionWHEP pull source (newsource-whepfeature, included insource-all): a configured stream sourceurl = "whep://[token@]host:port/whep/<stream>"pulls media from an upstream WHEP endpoint — the declarative, static form ofcascade-pull— and takes part in the full source lifecycle:on_demandstart/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 sharedlivetwo::whep::forward_rtcp_to_peerhelper used both here and by whepfrom's TCP output.liveionon-demand sources:[stream.<name>] on_demand = truekeeps 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 againon_demand_close_after_msafter the last one leaves. The first subscriber waits up toon_demand_start_timeout_msfor 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 showsstandby/on-demandbadges for these streams andconfigfor other provisioned streams;api::response::Streamgained matchingprovisioned/onDemandfields.liveionstream-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 asLIVE777_SESSION(configured sources reportvirtual-source) and, for stop events, the reason (peer-closed/api-deleted/idle-timeout). Scripts receive<event> <stream> [reason]as argv and the same values asLIVE777_EVENT/LIVE777_STREAM/LIVE777_REASON; execution is a single FIFO queue (global hooks first, then per-stream, in configured order) with per-scripttimeout_msand anon_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, includingwhipintowhose ICE servers were previously hardcoded. The flag has no default value — the tools gather host candidates only unless one or more--ice-serverflags are given (an empty string explicitly disables ICE servers), since WHIP/WHEP endpoints already advertise their own ICE servers via Link headers. Thesynth://input URL takes a repeatableice=<spec>parameter that overrides the CLI list for that input. Specs are parsed, validated, and converted through the sharediceservercrate (IceServer::from_str,to_rtc_ice_servers,default_rtc_ice_servers,DEFAULT_ICE_SERVER_URL), and the CLI flag itself is the sharediceserver::IceServerArgsclap struct. liveionstream media statistics (#252):StreamandSessionAPI objects gained astatsfield (bytes/packetscumulative,bitratein bits per second, re-sampled every 2 seconds; byte counts are RTP wire sizes and exclude NACK retransmissions). Stream-level totals (stats.publishfor the inbound publisher side,stats.subscribefor 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 counterlive777_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;statsScopereports whether stats are single-node (node) or liveman's cluster-wide node-work sum (clusterNodeWork, where cascade hops count once per relay node).liveionsource-change hooks: the newon_source_changedhook (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 whenon_error = "stop", before the pipeline is touched (the API call fails with500). 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 andLIVE777_STREAM/LIVE777_SOURCE_TIER/LIVE777_SOURCE_WIDTH/LIVE777_SOURCE_HEIGHT/LIVE777_SOURCE_FPS/LIVE777_SOURCE_BITRATEas 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.livehalnative capture + hardware-encoder pipeline: a camera now streams in-process, with no ffmpeg helper process. Newnative-sourceinfrastructure with capture backends (capture-libcamera,capture-v4l2) and encoder backends (encoder-v4l2-m2m,encoder-rkmpp,encoder-rdk), plusnative-rpi/native-generic-v4l2/native-rdk/native-rkmppplatform 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 = 0switches to on-request keyframes). The release workflow publishes prebuilt-rpiand-rkmpptarballs, and per-platform cross-build images are published on ghcr.liveionembedded RTSP server (newrtspfeature): 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 = falseopts 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}/tierlists and applies named source presets — bitrate-only tiers retune the encoder in place, while tiers carryingwidth/height/fpsrebuild the capture+encoder pipeline with rollback on failure (subscribers stay attached across the sub-second gap) — andGET /api/sources/{stream}/bitratereports read-only encoder telemetry. liveionWHIP/WHEP server sessions now run as an ICE Lite agent.- New tools:
livewrk(WHIP/WHEP load testing withwhipandwhepsubcommands, named afterwrk),whipsynth(synthetic test-frame WHIP publisher) andwhepprobe(WHEP stream prober with human/JSON output) — the latter two, pluswhipinto's newsynth://<vcodec>input, publish in-process generated test frames with no external encoder (rsmpegfeature) — as well aswhepwright(Playwright-driven browser WHEP playback tester,whepwrightfeature) anddatachannel_loadtest. whepfromgained DataChannel reception and a UDP output extension.GET /api/inforeports the server version and compiled-in feature set, and every binary's--versionoutput now also prints the enabled features.- The web player stack migrated from SolidJS to Vue 3 and was split into the framework-agnostic
player-coreengine and the@binbat/whep-player-vuecomponent library (WhepPlayer,StatsForNerds, and a standalone query-param-driven player page served as/tools/player.htmlin both admin apps). - WebUI additions: cascade management in the dashboard, a
lowlatencyquery 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:
whipintosupports AV1 input at RTP payload sizes above 1200 bytes, and thertc-rtpAV1 depacketizer was patched so OBS AV1 recordings produce playable files. - Size-optimized embedded builds via the opt-in
release-sizeCargo profile plus UPX packing recipes (just build-size/pack-size, cross variants, and the device shortcutsrpi-pack-size/rdk-pack-size/v4l2-pack-size). - livetwo WHEP clients (
whepfrom,livewrk whep) log one-way delay derived from RTCP Sender Reports.
Changed
liveionnow 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) with409 Conflict, instead of mixing both publishers' tracks into every subscriber.liveionconfigured sources (always-on and on-demand alike) now emitPublishStarted/PublishStoppedlifecycle events with the synthesizedvirtual-sourcesession id when they start/stop, andmetrics::PUBLISHcounts them. Recording of on-demand streams matchingrecorder.auto_streamsis driven by these publish events (record only while the source is active) instead ofStreamCreated/StreamDeleted.liveionstream lifecycle events are now typed (liveion::event::Event) and travel on a single manager-wide broadcast bus:StreamDeletedemission 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.liveionsubscribe-side RTP write errors are now retried with a 3s bound while the peer may still be coming up, instead of being classified by matchingwebrtccrate error strings.liveionURL-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.liveionnow exposes a single SSE endpoint/api/sse/streamsthat pushes a full snapshot of all streams whenever stream state changes.net4mqttxdata 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-suppliedaliasfield inside the payload.livemanconsumesnet4mqttxdatastreamsmessages and uses the message metadata as the node alias.livemanstatic nodes now support amodefield ("poll"or"sse", default"poll").pollnodes continue to be updated by periodic HTTP polling, whilessenodes subscribe to the upstream/api/sse/streamsendpoint for stream-state snapshots.net4mqttnodes are unaffected and still update throughxdata.liveionSSE/api/sse/streamsandnet4mqttxdatastreamsnow deduplicate snapshots: a new message is only emitted when the actual stream-state snapshot differs from the previous one.liveionnet4mqttxdatastreamspayload and vdata online payload no longer contain a redundantaliasfield; the sender identity is provided by the MQTT topic/channel metadata.liveionnet4mqttxdata channel is now bounded (capacity 64) with backpressure: outdated snapshots are dropped when the channel is full.livemanSSE 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.livemanno longer polls/api/strategyfor dynamically discoverednet4mqttnodes.liveionSSE/api/sse/streamsand thenet4mqttxdatastreamschannel 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 withPublishStopped/SessionStopReason::Replaced, and same-codec takeovers are seamless to subscribers. Streams opt out withstrategy.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/authJWT verification was replaced with a minimal ring-based HS256 implementation, dropping thejsonwebtokendependency.
Fixed
- Fixed
liveionstream 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 losePublishStopped/SubscribeStoppedlifecycle events and skip the cascade session DELETE to the downstream node, leaving a zombie publisher there until ICE timed out.closenow removes sessions through the same cleanup path as explicit removals. - Fixed
liveionevent 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.
liveionnow logs a warning when a peer connection enters theDisconnectedstate (previously a lifecycle blind spot until it escalated toFailed), 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 transientDisconnectedstate instead of permanently stopping on the first write error during it.- Fixed
liveionleaking 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
net4mqttwhenXDataConfig.receiverwas not provided. - Changed MQTT subscribe/publish calls in
net4mqttto propagate errors instead of panicking on connection failures. - Fixed
livemanstorage update logic so that stale stream/session mappings for a node are cleared before applying a new snapshot. - Fixed task leaks in
liveionnet4mqtt reconnection logic. - Fixed
GET /api/sources,GET /api/sources/{stream}andGET /api/sources/{stream}/statedropping the request (handler task panic, empty reply) whenever a source existed: source state moved fromtokio::sync::RwLock::blocking_read— which panics inside an async runtime — to a plainstd::sync::RwLock. - Fixed
liveionfailing to compile with onlysource-sdp(missingrtsp_codecmodule gate) or onlysource(missinglivetwodependency), and cleaned up thedead_code/unused warnings across feature combinations by narrowingcfggates to the features that actually use each item; the unusedPeerForward::first_video_codechelper was removed. - Fixed
libwishWHIP/WHEP HTTP requests having no timeout: a peer that accepts the connection but never responds could wedgewhepfrom/whipinto, cascade setup, and a configured source's reconnect loop orstop()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
Laggederror; it now skips the lagged messages and keeps forwarding. - Source URLs with embedded credentials (
rtsp://user:pass@…,whep://token@…) are now redacted inliveionlog lines instead of being written out in full. - Fixed livetwo WHEP client peers hardcoding
stun:stun.l.google.com:19302for ICE gathering:livetwo::whep::setup_whep_peernow takes the ICE server list from its caller instead. TheliveionWHEP pull source uses the server's own[[ice_servers]]config, andwhepfrom,whepprobeandlivewrk whepgained 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, andliveionconfig-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_mapnow returnResult, 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
liveionWHEP pull source's outgoing peer now also binds the UDP sockets from[webrtc] ice_udp_addrs(previously only theLIVE777_WEBRTC_ICE_UDP_ADDRSenv var was consulted), and no longer joins the upstream's WHEP control group with an unnecessarycontrolDataChannel. - 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