Skip to content

Data streams v2 uniffi - #1286

Merged
1egoman merged 28 commits into
mainfrom
data-streams-v2-uniffi
Sep 1, 2026
Merged

Data streams v2 uniffi#1286
1egoman merged 28 commits into
mainfrom
data-streams-v2-uniffi

Conversation

@1egoman

@1egoman 1egoman commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Adds data streams v2 to the livekit-uniffi crate, and exposes it fairly similarly to how data tracks works.

I've also for now added a python test script (It's the most complete bindgen set up in this project which I am familiar with) which exercises this api as an example of what it would look like in practice. I'll remove this before an eventual merge, but I thought it would be useful for reviewers:

$ python3 datastream_uniffi_test.py
--- OUTGOING:
PACKETS: [b'jP\n$4a501804-960c-4d54-94df-81784ffab6b1\x10\xc6\xa5\xb0\xaf\xf93\x1a\x04test"\ntext/plain(\x0bJ\x00Z\x0bhello world']
--- INCOMING:
TEXT STREAM OPENED: alice CONTENTS: hello world

Base automatically changed from data-streams-v2 to main July 28, 2026 20:11
@1egoman
1egoman force-pushed the data-streams-v2-uniffi branch from 29ffd71 to 7ef488b Compare July 28, 2026 20:52
@1egoman
1egoman marked this pull request as ready for review July 28, 2026 20:52
@1egoman
1egoman requested a review from ladvoc as a code owner July 28, 2026 20:52
@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Changeset ✓

This PR includes a changeset covering all affected packages:

Package Bump
livekit patch
livekit-api patch
livekit-data-stream patch
livekit-datatrack patch
livekit-ffi patch
livekit-net patch
livekit-signaling patch
livekit-token-source patch
livekit-uniffi patch

@1egoman
1egoman requested a review from pblazej July 28, 2026 20:52
devin-ai-integration[bot]

This comment was marked as resolved.

@pblazej

pblazej commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

My suggested approach here is to just open a draft PR to consumers e.g. swift and then polish it - that's what I did for data tracks before.

Otherwise the reviewer must generate the bindings anyway, as it's very hard to predict the edge cases around concurrency, memory management, etc.

I'd be super happy to take a look at both in parallel.

@1egoman
1egoman force-pushed the data-streams-v2-uniffi branch from f28049b to d1c8eff Compare July 30, 2026 18:10
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@1egoman
1egoman marked this pull request as draft July 31, 2026 20:49
@1egoman
1egoman removed the request for review from ladvoc July 31, 2026 20:49
@1egoman
1egoman force-pushed the data-streams-v2-uniffi branch from b0113f2 to 76f1e16 Compare July 31, 2026 20:52
1egoman added a commit that referenced this pull request Aug 3, 2026
… `livekit-data-stream` (#1304)

While working on #1286, I realized I made a bit of a mess of the "internal" data stream concept when extracting `livekit-data-stream` out into its own crate. This pull request attempts to address this.

**Currently, what happens today:** `livekit-data-stream`'s `IncomingDataStreamManager` takes a list of `INTERNAL_DATA_STREAM_TOPICS`. These are topics used internally for v2 rpc requests / responses and messages associated with these data streams
are not exposed to the user. They are filtered in two places:
- Within `IncomingDataStreamManager` to conditionally expose both
`InputEvent::ChunkReceived` and `InputEvent::TrailerReceived` for non
internal topics
- Within `RoomSession` to expose which `InputEvent::StreamOpened` events
are exposed to the user. Doing this here meant that "internal" data
streams could be intercepted at this level and fed into rpc / etc.

This is messy - it's a little hard to follow and the filtering is split
into these two places / some code duplication. What really made this
untenable though was that exposing this internal state over uniffi in
#1286 became particularly challenging, since you _do_ want to expose
"internal" events downstream in this case - if you didn't, the uniffi
consuming client couldn't handle rpc v2 / etc!

**What this pull request does:** Now, `livekit-data-stream` knows
nothing about "internal" or "not internal" data streams, and includes
the raw `topic` of each stream in the associated event. This allows all
the filtering to be centralized downstream in `RoomSession`. This fixes
all of these previously mentioned problems and is a lot easier to reason
about!
@1egoman
1egoman force-pushed the data-streams-v2-uniffi branch 2 times, most recently from e121d38 to 1fe0eba Compare August 6, 2026 20:47
@@ -6,6 +6,48 @@ android = true
package_name = "io.livekit.uniffi"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be com.livekit.uniffi in the future?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, why? 😃

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just because our branding changed from livekit.io to livekit.com. Don't know if this is related here though.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah yes there's a redirect now

@pblazej pblazej left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I integrated this on the Swift side (livekit/client-sdk-swift#1075) and it works end-to-end. Compression and single-packet inlining both kick in correctly once capabilities round-trip, which I verified on the wire.

Below are five gaps in the FFI surface I hit while wiring it up, ordered by what they cost a host rather than by size. None of them blocks the integration; #1 and #2 are the two that cost correctness rather than convenience, and both look cheaper to close now than after this ships and hosts have worked around them.

Comment thread livekit-uniffi/src/data_stream/incoming.rs
Comment thread livekit-uniffi/src/data_stream/outgoing.rs Outdated
Comment thread livekit-uniffi/src/data_stream/common.rs Outdated
Comment thread livekit-uniffi/src/data_stream/incoming.rs
Comment thread livekit-uniffi/src/data_stream/incoming.rs
@pblazej

pblazej commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Pushed 7a2505f, which makes on_packets_available an async fn — a synchronous callback can't honor the contract documented above it on hosts whose transport is async — with the reasoning, verification and open Kotlin/Dart question in the thread below; revert freely if you'd rather it went another way.

@1egoman

1egoman commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

FYI @pblazej - 1da01b4 was an issue I found when updating the android implementation to take into account the changes from your review. I think it may have some implications on the swift version as it changes the format of the Packet::Trailer message to include encryption_type. See the commit message for more about the exact context about what was fixed.

@pblazej

pblazej commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

FYI @pblazej - 1da01b4 was an issue I found

👏 I don't see anything else here at least for Swift, ready for final review?

@1egoman
1egoman marked this pull request as ready for review August 24, 2026 19:47
@1egoman
1egoman force-pushed the data-streams-v2-uniffi branch from a3bc76a to f8b3fed Compare August 25, 2026 15:20
@1egoman

1egoman commented Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

(rebased on top of latest main)

1egoman and others added 11 commits August 26, 2026 12:14
… FFI

The incoming manager previously signaled wire-level stream closure only via
the deprecated TrailerReceived output, which the FFI layer intentionally does
not forward — leaving hosts that deliver streams on ordered topics (e.g.
transcription) no way to know when a stream's handler chain can advance, so a
still-open stream would head-of-line-block every later stream from that
sender. A trailer alone is also insufficient: inline single-packet streams
never receive one.

Add a StreamClosed output event emitted exactly once per opened stream on
every terminal path (trailer close, inline completion, error, abort) and
surface it through IncomingDataStreamManagerDelegate::on_stream_closed plus a
next_closed_stream() pull on the polled adapter.
…tch one-shot sends

OutgoingDataStreamManagerDelegate::on_packets_available returned (), so a
packet that never reached the wire could not fail the originating call:
write() couldn't throw, is_open() couldn't go false on a send failure, and the
responder acked once the host had merely buffered — letting a producer looping
on write() queue unboundedly. The delegate now returns
Result<(), PacketDeliveryError> (a dedicated error carrying a host-provided
reason, convertible into DataStreamError::Internal); throwing it fails the
originating send_*/write call with SendFailed and closes the affected stream,
and returning only after handing packets to the transport is what provides
back-pressure.

The Vec<Bytes> signature also always carried exactly one packet. Keep the
shape (matching the data-track PacketsAvailable interface) but make it true:
the packet channel now carries ordered batches acknowledged as a whole.
One-shot sends (send_text/send_bytes) emit their entire stream — header,
chunks, trailer — as a single request, i.e. one FFI crossing per send; every
other call site (incremental writers, send_file's unbuffered streaming, inline
sends) sends vec![packet].
…ry it in mismatch errors

The FFI decode path hard-coded EncryptionType::None, and it cannot do better
from the bytes alone: encrypted_packet is a member of the DataPacket.value
oneof, so a host decrypting E2EE traffic replaces it with the decrypted stream
packet — by the time the bytes reach the FFI, the field is absent from the
wire format. That made the encryption guard in handle_chunk dead code over the
FFI while still reading as active.

handle_packet_received now requires the encryption type the host received (or
decrypted) the packet with, making the guard live. EncryptionTypeMismatch also
gains expected/received fields so hosts can report which types disagreed
instead of fabricating them (Swift's error carries both).
The cap has no setter, and on the host side it typically comes from
per-connection options that aren't final until connect and can differ between
sessions of the same host object. The obvious host implementation — construct
lazily, memoize for the object's lifetime — silently pins the cap to the first
session's value. Document the intended pattern (rebuild the manager per
session) instead of adding reconfiguration complexity.
There was no way to observe how many incoming streams are open, so tests
exercising the abort paths had to infer "open" by signalling from inside a
handler — which measures handler-dispatched rather than descriptor-registered
and breaks if the two ever move relative to each other.

The count is answered through the manager's input queue, so it is processed
in order with previously enqueued packets: feed a header (or an abort), then
await open_stream_count() to know it has landed, no sleeps or handler
side-channels needed. Inline single-packet streams complete during header
handling and are never counted.
…contract

The delegate documents that it returns only once the packets have reached the
transport, which is what orders packets, bounds a producer, and lets a failed
send fail the originating `send_*`/`write`. A synchronous callback can't deliver
that on hosts whose transport is async: the Swift SDK has no synchronous send
path, so honoring it would mean blocking the calling thread on an async result —
a Rust runtime thread, via a synchronisation primitive that SDK forbids. The
practical outcome was that hosts acknowledged on buffering, leaving
back-pressure and transport errors unimplemented.

uniffi supports async methods on foreign traits: it applies `#[async_trait]` to
the generated impl and dispatches through `foreign_async_call`, so the trait
stays dyn-compatible. Awaiting the call in the pump keeps packets strictly
ordered, since the next one isn't pulled until this returns.

`async-trait` was already in the lockfile through livekit-api and livekit-net.
The in-tree implementors — the Dart polling adapter and the test doubles —
become `async fn` and are otherwise unchanged.

Verified against the Swift SDK (client-sdk-swift#1075): generates
`func onPacketsAvailable(packets:) async throws`, lets the host delete its
ordering pump entirely, and a stream whose transport goes away now fails its
`write` and reports `isOpen == false`, neither of which was observable before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
handle_chunk rejects a chunk whose encryption doesn't match its stream's
header, but trailers carried no encryption type at all — so in an E2EE room,
an unencrypted peer could close another participant's encrypted stream cleanly
and inject trailer attributes while doing so. The SDKs' hand-rolled v1
implementations (web, and the Android port's SDK-side check) validated
trailers; the core did not, which blocked deleting those host-side checks.

Packet::Trailer now carries the encryption type like Header and Chunk, the
livekit crate threads it through Session/Engine events (it was already in
hand at the emit site), and the manager rejects a mismatched trailer with
EncryptionTypeMismatch before merging its attributes.
The arm64-v8a slice is 1490 KiB with the data streams v2 surface (async
foreign-trait dispatch, stream-closed and open-stream-count, trailer
encryption checks), over the 1280 KiB budget set in May before that work.
cargo's strip = "symbols" is already applied to the artifact, so AGP's strip
pass has nothing further to remove — the growth is real surface, landed
intentionally.
@1egoman
1egoman force-pushed the data-streams-v2-uniffi branch from 53c9e65 to 72bdb58 Compare August 26, 2026 16:14
@1egoman

1egoman commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

(rebased on top of latest main)

@pblazej
pblazej requested a review from hiroshihorie August 27, 2026 07:06
@pblazej

pblazej commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

@1egoman completing flutter would be even better! 💯 cc @hiroshihorie

@MaxHeimbrock
MaxHeimbrock requested a review from jhugman as a code owner August 27, 2026 10:07
@MaxHeimbrock
MaxHeimbrock force-pushed the data-streams-v2-uniffi branch from 646944e to 71a8d76 Compare August 27, 2026 10:10
@MaxHeimbrock
MaxHeimbrock force-pushed the data-streams-v2-uniffi branch from 71a8d76 to 37f3f6c Compare August 27, 2026 10:12
@1egoman
1egoman merged commit c5844f4 into main Sep 1, 2026
26 of 27 checks passed
@1egoman
1egoman deleted the data-streams-v2-uniffi branch September 1, 2026 19:33
@knope-bot knope-bot Bot mentioned this pull request Sep 1, 2026
alan-george-lk pushed a commit that referenced this pull request Sep 8, 2026
> [!IMPORTANT]
> Merging this pull request will create these releases

# livekit-datatrack 0.1.15 (2026-09-08)
## Features

- Removes livekit-runtime and converts this package to be tokio only
again - #1375 (@1egoman)

## Fixes

- Add data streams v2 to exposed uniffi interface - #1286 (@1egoman)
- Add the `PASSTHROUGH` encoding preset and remove the unused
`UpdateEgressRequest` from the generated protocol
# livekit-uniffi 0.1.10 (2026-09-08)
## Features

- Removes livekit-runtime and converts this package to be tokio only
again - #1375 (@1egoman)

## Fixes

- Add data streams v2 to exposed uniffi interface - #1286 (@1egoman)
- Lower the Android UniFFI AAR minSdk from 24 to 21
- Add the `PASSTHROUGH` encoding preset and remove the unused
`UpdateEgressRequest` from the generated protocol
- Add `self_test_http_get` / `self_test_ws_echo` / `has_http_client` /
`has_ws_client` UniFFI exports so foreign hosts can exercise the
transport seam end-to-end.
- Attach Dart/Flutter cdylib assets to releases and prepare
livekit_uniffi for pub.dev publishing
- Update Android's JNA dependency version to 5.19.1 to support 16KB page
sizes
# livekit-token-source 0.1.3 (2026-09-08)
## Features

- Removes livekit-runtime and converts this package to be tokio only
again - #1375 (@1egoman)

## Fixes

- Add data streams v2 to exposed uniffi interface - #1286 (@1egoman)
# livekit-protocol 0.7.13 (2026-09-08)
## Features

- Add the `PASSTHROUGH` encoding preset and remove the unused
`UpdateEgressRequest` from the generated protocol
# livekit-common 0.1.3 (2026-09-08)
## Fixes

- Add the `PASSTHROUGH` encoding preset and remove the unused
`UpdateEgressRequest` from the generated protocol
# livekit-signaling 0.1.1 (2026-09-08)
## Features

- Removes livekit-runtime and converts this package to be tokio only
again - #1375 (@1egoman)

## Fixes

- Add data streams v2 to exposed uniffi interface - #1286 (@1egoman)

### Moves the signalling client into a new `livekit-signaling` crate.
livekit-api

re-exports it under the historical `livekit_api::signal_client` path,
now marked
deprecated: it is internal SDK API, and dependents should use
livekit-signaling
directly. livekit-api no longer depends on livekit-net.

Also drops two dependencies that were declared but never used:
`scopeguard` and
`bytes`.
# livekit 0.9.0 (2026-09-08)
## Breaking Changes

- Removes livekit-runtime and converts this package to be tokio only
again - #1375 (@1egoman)

## Fixes

- Add data streams v2 to exposed uniffi interface - #1286 (@1egoman)
- Add the `PASSTHROUGH` encoding preset and remove the unused
`UpdateEgressRequest` from the generated protocol
- Fix pre-encoded frame segfault on macOS
- Handle capture of dmabuf using existing capture path
- Add `self_test_http_get` / `self_test_ws_echo` / `has_http_client` /
`has_ws_client` UniFFI exports so foreign hosts can exercise the
transport seam end-to-end.

### Make AdmProxy worker-thread-affine: all platform ADM access now
happens on the WebRTC worker thread, matching the ADM threading
contract.

- The platform ADM is now created lazily on the first PlatformAudio
acquire on all platforms, so apps that never use platform audio never
construct it.
- Fixes Android platform recording delivering no audio: the audio
transport was never registered on the lazily created ADM.
- Fixes a shutdown race by keeping the runtime threads alive as long as
Rust can reach the audio device controller.
- Adds a `platform_audio` example exercising the PlatformAudio API and
the worker-thread marshaling.

### Add agent guidance for detecting and preventing memory-lifecycle
regressions in

Rust, FFI, and native WebRTC code.

### Close peer connections before awaiting signal teardown

`SessionInner::close` released the peer connections only after two
awaits that can block
indefinitely, so cancelling `close()` — for example by wrapping it in a
timeout — left the
transports open and their ICE UDP sockets bound for the lifetime of the
process. Long-lived
clients eventually exhausted their file descriptors. The transports are
now closed before
the first await, which makes the teardown safe to cancel.

### Moves the internal region-discovery cache into a new
`livekit-region` crate. No

public API or behaviour change.

### Moves the signalling client into a new `livekit-signaling` crate.
livekit-api

re-exports it under the historical `livekit_api::signal_client` path,
now marked
deprecated: it is internal SDK API, and dependents should use
livekit-signaling
directly. livekit-api no longer depends on livekit-net.

Also drops two dependencies that were declared but never used:
`scopeguard` and
`bytes`.

### Fix CUDA and FFI resource cleanup during SDK shutdown.

NVIDIA encoder and decoder factories now share a reference-counted CUDA
context
and destroy it when the final factory is dropped. FFI shutdown now
releases
leftover handles one at a time so nested `drop_handle` calls do not
re-enter
`DashMap::clear()`. Adds regression coverage for FFI-handle, watcher,
and
configuration cleanup during disposal.

### Expose network_type on IceCandidateStats

Chromium's local `RTCIceCandidateStats` carries a non-standard
`networkType` field (WiFi,
cellular, ethernet, etc.), but `IceCandidateStats` had no place to put
it, so it was silently
dropped during `get_stats()` deserialization. Adds `network_type:
Option<String>` to the struct;
non-breaking since it already derives `#[serde(default)]`.

### Fix room-session and data-channel leaks across connect/disconnect
cycles.

The E2EE manager callback now captures `RoomSession` weakly so the
session can
drop after disconnect. Data-channel observer callbacks are cleared
during RTC
teardown so the observer/callback cycle cannot keep peer connections
alive.
Adds regression coverage for room-session destruction and data-channel
callback
cleanup.

### Fix native video-source lifecycle and NVENC initialization failure
handling.

The raw-video keepalive task now uses a weak liveness check and defers
its
black I420 buffer allocation until source liveness is confirmed, so
dropping
an unused source releases its resources. `nvEncInitializeEncoder`
failures now
propagate instead of leaving the encoder half-initialized.
# livekit-ffi 0.12.77 (2026-09-08)
## Features

- Removes livekit-runtime and converts this package to be tokio only
again - #1375 (@1egoman)

## Fixes

- Add data streams v2 to exposed uniffi interface - #1286 (@1egoman)
- Add the `PASSTHROUGH` encoding preset and remove the unused
`UpdateEgressRequest` from the generated protocol
- Fix pre-encoded frame segfault on macOS
- Handle capture of dmabuf using existing capture path
- Add `self_test_http_get` / `self_test_ws_echo` / `has_http_client` /
`has_ws_client` UniFFI exports so foreign hosts can exercise the
transport seam end-to-end.

### Make AdmProxy worker-thread-affine: all platform ADM access now
happens on the WebRTC worker thread, matching the ADM threading
contract.

- The platform ADM is now created lazily on the first PlatformAudio
acquire on all platforms, so apps that never use platform audio never
construct it.
- Fixes Android platform recording delivering no audio: the audio
transport was never registered on the lazily created ADM.
- Fixes a shutdown race by keeping the runtime threads alive as long as
Rust can reach the audio device controller.
- Adds a `platform_audio` example exercising the PlatformAudio API and
the worker-thread marshaling.

### Add agent guidance for detecting and preventing memory-lifecycle
regressions in

Rust, FFI, and native WebRTC code.

### Close peer connections before awaiting signal teardown

`SessionInner::close` released the peer connections only after two
awaits that can block
indefinitely, so cancelling `close()` — for example by wrapping it in a
timeout — left the
transports open and their ICE UDP sockets bound for the lifetime of the
process. Long-lived
clients eventually exhausted their file descriptors. The transports are
now closed before
the first await, which makes the teardown safe to cancel.

### Moves the internal region-discovery cache into a new
`livekit-region` crate. No

public API or behaviour change.

### Moves the signalling client into a new `livekit-signaling` crate.
livekit-api

re-exports it under the historical `livekit_api::signal_client` path,
now marked
deprecated: it is internal SDK API, and dependents should use
livekit-signaling
directly. livekit-api no longer depends on livekit-net.

Also drops two dependencies that were declared but never used:
`scopeguard` and
`bytes`.

### Fix CUDA and FFI resource cleanup during SDK shutdown.

NVIDIA encoder and decoder factories now share a reference-counted CUDA
context
and destroy it when the final factory is dropped. FFI shutdown now
releases
leftover handles one at a time so nested `drop_handle` calls do not
re-enter
`DashMap::clear()`. Adds regression coverage for FFI-handle, watcher,
and
configuration cleanup during disposal.

### Expose network_type on IceCandidateStats

Chromium's local `RTCIceCandidateStats` carries a non-standard
`networkType` field (WiFi,
cellular, ethernet, etc.), but `IceCandidateStats` had no place to put
it, so it was silently
dropped during `get_stats()` deserialization. Adds `network_type:
Option<String>` to the struct;
non-breaking since it already derives `#[serde(default)]`.

### Fix room-session and data-channel leaks across connect/disconnect
cycles.

The E2EE manager callback now captures `RoomSession` weakly so the
session can
drop after disconnect. Data-channel observer callbacks are cleared
during RTC
teardown so the observer/callback cycle cannot keep peer connections
alive.
Adds regression coverage for room-session destruction and data-channel
callback
cleanup.

### Fix native video-source lifecycle and NVENC initialization failure
handling.

The raw-video keepalive task now uses a weak liveness check and defers
its
black I420 buffer allocation until source liveness is confirmed, so
dropping
an unused source releases its resources. `nvEncInitializeEncoder`
failures now
propagate instead of leaving the encoder half-initialized.
# livekit-net 0.1.3 (2026-09-08)
## Features

- Removes livekit-runtime and converts this package to be tokio only
again - #1375 (@1egoman)

## Fixes

- Add data streams v2 to exposed uniffi interface - #1286 (@1egoman)
- Add `self_test_http_get` / `self_test_ws_echo` / `has_http_client` /
`has_ws_client` UniFFI exports so foreign hosts can exercise the
transport seam end-to-end.
# livekit-data-stream 0.1.4 (2026-09-08)
## Fixes

- Add data streams v2 to exposed uniffi interface - #1286 (@1egoman)
- Add the `PASSTHROUGH` encoding preset and remove the unused
`UpdateEgressRequest` from the generated protocol
# libwebrtc 0.3.47 (2026-09-08)
## Features

- Handle capture of dmabuf using existing capture path
- Removes livekit-runtime and converts this package to be tokio only
again - #1375 (@1egoman)

### Expose network_type on IceCandidateStats

Chromium's local `RTCIceCandidateStats` carries a non-standard
`networkType` field (WiFi,
cellular, ethernet, etc.), but `IceCandidateStats` had no place to put
it, so it was silently
dropped during `get_stats()` deserialization. Adds `network_type:
Option<String>` to the struct;
non-breaking since it already derives `#[serde(default)]`.

## Fixes

- Fix pre-encoded frame segfault on macOS

### Make AdmProxy worker-thread-affine: all platform ADM access now
happens on the WebRTC worker thread, matching the ADM threading
contract.

- The platform ADM is now created lazily on the first PlatformAudio
acquire on all platforms, so apps that never use platform audio never
construct it.
- Fixes Android platform recording delivering no audio: the audio
transport was never registered on the lazily created ADM.
- Fixes a shutdown race by keeping the runtime threads alive as long as
Rust can reach the audio device controller.
- Adds a `platform_audio` example exercising the PlatformAudio API and
the worker-thread marshaling.

### Add agent guidance for detecting and preventing memory-lifecycle
regressions in

Rust, FFI, and native WebRTC code.

### Fix CUDA and FFI resource cleanup during SDK shutdown.

NVIDIA encoder and decoder factories now share a reference-counted CUDA
context
and destroy it when the final factory is dropped. FFI shutdown now
releases
leftover handles one at a time so nested `drop_handle` calls do not
re-enter
`DashMap::clear()`. Adds regression coverage for FFI-handle, watcher,
and
configuration cleanup during disposal.

### Fix room-session and data-channel leaks across connect/disconnect
cycles.

The E2EE manager callback now captures `RoomSession` weakly so the
session can
drop after disconnect. Data-channel observer callbacks are cleared
during RTC
teardown so the observer/callback cycle cannot keep peer connections
alive.
Adds regression coverage for room-session destruction and data-channel
callback
cleanup.

### Fix native video-source lifecycle and NVENC initialization failure
handling.

The raw-video keepalive task now uses a weak liveness check and defers
its
black I420 buffer allocation until source liveness is confirmed, so
dropping
an unused source releases its resources. `nvEncInitializeEncoder`
failures now
propagate instead of leaving the encoder half-initialized.
# webrtc-sys 0.3.44 (2026-09-08)
## Features

- Handle capture of dmabuf using existing capture path

## Fixes

- Fix pre-encoded frame segfault on macOS

### Make AdmProxy worker-thread-affine: all platform ADM access now
happens on the WebRTC worker thread, matching the ADM threading
contract.

- The platform ADM is now created lazily on the first PlatformAudio
acquire on all platforms, so apps that never use platform audio never
construct it.
- Fixes Android platform recording delivering no audio: the audio
transport was never registered on the lazily created ADM.
- Fixes a shutdown race by keeping the runtime threads alive as long as
Rust can reach the audio device controller.
- Adds a `platform_audio` example exercising the PlatformAudio API and
the worker-thread marshaling.

### Add agent guidance for detecting and preventing memory-lifecycle
regressions in

Rust, FFI, and native WebRTC code.

### Fix CUDA and FFI resource cleanup during SDK shutdown.

NVIDIA encoder and decoder factories now share a reference-counted CUDA
context
and destroy it when the final factory is dropped. FFI shutdown now
releases
leftover handles one at a time so nested `drop_handle` calls do not
re-enter
`DashMap::clear()`. Adds regression coverage for FFI-handle, watcher,
and
configuration cleanup during disposal.

### Fix native video-source lifecycle and NVENC initialization failure
handling.

The raw-video keepalive task now uses a weak liveness check and defers
its
black I420 buffer allocation until source liveness is confirmed, so
dropping
an unused source releases its resources. `nvEncInitializeEncoder`
failures now
propagate instead of leaving the encoder half-initialized.
# livekit-region 0.1.1 (2026-09-08)
## Fixes

### Moves the internal region-discovery cache into a new
`livekit-region` crate. No

public API or behaviour change.
# livekit-api 0.7.0 (2026-09-08)
## Breaking Changes

- Removes livekit-runtime and converts this package to be tokio only
again - #1375 (@1egoman)

## Fixes

- Add data streams v2 to exposed uniffi interface - #1286 (@1egoman)
- Add the `PASSTHROUGH` encoding preset and remove the unused
`UpdateEgressRequest` from the generated protocol
- Add `self_test_http_get` / `self_test_ws_echo` / `has_http_client` /
`has_ws_client` UniFFI exports so foreign hosts can exercise the
transport seam end-to-end.
- Update sip_busy test GRPc code: failed_precondition ->
failed_precondition

### Moves the internal region-discovery cache into a new
`livekit-region` crate. No

public API or behaviour change.

### Moves the signalling client into a new `livekit-signaling` crate.
livekit-api

re-exports it under the historical `livekit_api::signal_client` path,
now marked
deprecated: it is internal SDK API, and dependents should use
livekit-signaling
directly. livekit-api no longer depends on livekit-net.

Also drops two dependencies that were declared but never used:
`scopeguard` and
`bytes`.
# livekit-token 0.1.2 (2026-09-08)
## Fixes

- Add the `PASSTHROUGH` encoding preset and remove the unused
`UpdateEgressRequest` from the generated protocol

Co-authored-by: knope-bot[bot] <152252888+knope-bot[bot]@users.noreply.github.com>
1egoman added a commit that referenced this pull request Sep 8, 2026
The release page formatting is inconsistient - some entries are
changelog bullets, others are `####` headings followed by further
markdown.

That is knope's rendering rule, not a knope bug. A changeset whose
summary is **one line** becomes a bullet:

```
- Add data streams v2 to exposed uniffi interface - #1286 (@1egoman)
```

A summary spanning **more than one line** is promoted to a heading, with
everything after the first line dropped below it as loose body text:

```
#### Moves the signalling client into a new `livekit-signaling` crate. livekit-api

re-exports it under the historical `livekit_api::signal_client` path, now marked
deprecated: ...
```

Markdown blocks (tables, bullet lists, bold-led paragraphs) guarantee
this, and they keep showing up in agent-written changesets — but a plain
hard line wrap is enough on its own. The changeset for #1406 was plain
text wrapped at 80 columns and still rendered as `#### Add agent
guidance for detecting and preventing memory-lifecycle regressions in`.

So this adds a rule under **Documenting changes** in `AGENTS.md`: write
the changeset summary as a single unwrapped line of plain prose, no
Markdown blocks (inline backticks are fine — they render correctly), and
keep detail in the PR description instead.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
pblazej added a commit that referenced this pull request Sep 9, 2026
The livekit-uniffi 0.1.10 release failed both size gates. Data streams v2
(#1286) bumped only ANDROID_SIZE_LIMIT_BYTES, leaving the iOS budget at
1152 KiB while the same async foreign-trait machinery landed in that slice
too, so it measured 1433 KiB. Android was a separate, ordinary 8 KiB of
creep past its 1536 KiB limit at 1544 KiB.

Both limits are re-baselined on the measured 0.1.10 sizes with 10% headroom,
rounded up to a 64 KiB boundary.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
pblazej added a commit that referenced this pull request Sep 9, 2026
The `livekit-uniffi` 0.1.10 release failed both size gates
([run](https://github.com/livekit/rust-sdks/actions/runs/34257920596)):

| Gate | Measured | Old limit | New limit | Headroom |
|---|---|---|---|---|
| iOS (`ios-arm64` framework binary) | 1433 KiB | 1152 KiB | **1600
KiB** | 11.6% |
| Android (`arm64-v8a` `.so`) | 1544 KiB | 1536 KiB | **1728 KiB** |
11.9% |

These are two different failures.

**iOS had a stale budget.** Data streams v2 (#1286) measured the
arm64-v8a slice and bumped `ANDROID_SIZE_LIMIT_BYTES` from 1280 KiB to
1536 KiB, but left `SPM_SIZE_LIMIT_BYTES` untouched at 1152 KiB. The
same async foreign-trait machinery landed in the iOS slice too —
Android's new budget absorbed it and iOS's did not, so the iOS limit had
been a week stale by the time the release cut. The 281 KiB overage is
that change arriving against an unbumped budget, not a new regression.

**Android was ordinary creep**, missing by 8 KiB.

Both limits are now re-baselined on the measured 0.1.10 sizes with 10%
headroom, rounded up to a 64 KiB boundary, and each comment records the
measurement it came from so the next bump has a baseline to reason from.

### Worth a follow-up

The reason a forgotten bump stayed invisible for a week is that these
gates only ever run at release time: `uniffi-packages.yml` triggers on
`release: [published]` / `workflow_dispatch`, and `uniffi-swift.yml` /
`uniffi-android.yml` are `workflow_call`-only. No PR executes
`swift-check-size`, so a stale budget surfaces only once a release tag
is already cut. Moving the gates earlier isn't free — it means building
the xcframework and AAR on PRs touching `livekit-uniffi` (~8 min each in
the release run), so it wants a path filter rather than running
repo-wide. Left out of this patch deliberately.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants