Skip to content

Data tracks UniFFI - #1034

Merged
ladvoc merged 70 commits into
mainfrom
ladvoc/data-tracks-uniffi
Jul 10, 2026
Merged

Data tracks UniFFI#1034
ladvoc merged 70 commits into
mainfrom
ladvoc/data-tracks-uniffi

Conversation

@ladvoc

@ladvoc ladvoc commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Summary of changes:

  • Exposes data tracks core functionality through livekit-uniffi
    • This will eventually enable the following clients to share the Rust implementation: Swift, Kotlin, React Native, Flutter
  • Minor changes to the data tracks crate to support this

Resolves CLT-2472

@ladvoc

ladvoc commented Jun 30, 2026

Copy link
Copy Markdown
Contributor Author

Quick-reconnect sync state. No way to build SyncState.publishDataTracks from the bindings. query_tracks() is exposed, but the uniffi DataTrackInfo drops pub_handle, so I can't construct PublishDataTrackResponse client-side the way JS/Rust do (their info carries the handle). Either expose pub_handle on the uniffi DataTrackInfo, or add a publish_responses_for_sync_state() returning the serialized responses (keeps the handle internal — probably cleaner).

Makes sense, I implemented the latter in 4f18c9c. This replaces the query_tracks method that was previously exported.

@ladvoc ladvoc closed this Jun 30, 2026
@ladvoc ladvoc reopened this Jun 30, 2026
@ladvoc

ladvoc commented Jun 30, 2026

Copy link
Copy Markdown
Contributor Author

Join-time tracks. No join handler on RemoteDataTrackManager, even though the reference SDK runs event_from_join on connect (rtc_session.rs:531) — so a foreign client joining a room with existing data tracks won't see them. Either add a handle_sfu_join, or just feed join.other_participants through the existing handle_sfu_participant_update (what JS effectively does). Identical under the hood — both hit event_from_participant_info. Preference?

Rust has these additional helpers to keep the interface between the rest of the client SDK and the data tracks managers as uniform as possible; instead of caring about specific fields in each response type, you hand over the whole response, and the logic of how the required information is extracted is kept as an implementation detail. A few other advantages:

  • If the data track managers ever need additional information not extracted from participant info, no update to this interface will be needed.
  • Local participant identity isn't needed when handling a join response but it is when handling a participant update; this gets modeled in the signature of each method.

I implemented handle_sfu_join_response in 8abcf90. While these helpers don't add any overhead in the Rust client integration since they accept the responses by reference, in an FFI context, there is additional overhead because the whole response must be serialized rather than just the relevant fields—I'm open to changing this if the additional overhead is a concern.

@ladvoc

ladvoc commented Jun 30, 2026

Copy link
Copy Markdown
Contributor Author

Caveats: no released livekit-server (incl. 1.13.2) fills ParticipantInfo.data_tracks in the join, so join discovery isn't verifiable e2e atm — is the SFU side still unreleased?

Data tracks is fully released in OSS and Cloud now so this would be unexpected. Do you have any repro steps for this?

@pblazej

pblazej commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Do you have any repro steps for this?

No repro — false alarm on my side. The join does carry data_tracks on 1.13.2; my test was discarding the returned LocalDataTrack (_ = try await publishDataTrack(...)), which unpublishes it on drop, so the track was already gone before the late joiner arrived. Holding it, join-time discovery works. Both handle_sfu_join_response and publish_responses_for_sync_state are wired up and green e2e now.

That RAII-on-drop is really the last open question: should FFI consumers mirror Rust's semantics (drop = unpublish) or go "publication-like" (JS keeps the publication in its manager until an explicit unpublish)? We went publication-like on the Swift side so callers aren't forced to retain the handle — this diff shows it: livekit/client-sdk-swift@3e3043e

Framing it as "what should the convention be" rather than "should Rust change": the core's RAII is correct/idiomatic for Rust consumers — the divergence is about what the FFI bindings present. JS and (now) Swift both land on publication-like, so there's a de-facto convention worth making explicit.

For concreteness, the Swift lifecycle surface:

API Semantics
publishDataTrack(name:) publication-like — SDK retains it; discarding the handle does not unpublish
track.unpublish() / await track.waitForUnpublish() explicit unpublish + await
withDataTrack(name:) { … } scoped RAII — auto-unpublishes when the block exits

withDataTrack is the compromise: it keeps Rust's RAII where it's genuinely a feature (an explicit lexical scope where auto-cleanup is exactly what you want), while the default publishDataTrack stays publication-like so discarding the handle doesn't surprise-unpublish.

@pblazej
pblazej marked this pull request as ready for review July 1, 2026 08:06

@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 don't see any functional gaps here preventing Swift integration

The rest is mostly consumer-side discussion (like the RAII pattern mentioned above).

Before merging please update both SPM_SIZE_LIMIT_BYTES and ANDROID_SIZE_LIMIT_BYTES with the final sizes.

Approximate sizes @ the latest commit:

  • SPM_SIZE_LIMIT_BYTES = 1152136
  • ANDROID_SIZE_LIMIT_BYTES = 1250710

@pblazej
pblazej requested a review from hiroshihorie July 7, 2026 13:46
@pblazej

pblazej commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

@hiroshihorie cc what's your take on applying that in flutter (as the infra is ~complete)?

devin-ai-integration[bot]

This comment was marked as resolved.

ladvoc and others added 5 commits July 9, 2026 14:33
Add subscribe_with_options on RemoteDataTrack and a DataTrackSubscribeOptions
record (buffer_size) so foreign consumers can tune the internal frame buffer,
matching the core and JS subscribe APIs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ladvoc
ladvoc force-pushed the ladvoc/data-tracks-uniffi branch from d0c8d7b to eda4a11 Compare July 9, 2026 21:45
devin-ai-integration[bot]

This comment was marked as resolved.

@ladvoc
ladvoc force-pushed the ladvoc/data-tracks-uniffi branch from 163cdd9 to 61bd528 Compare July 10, 2026 17:40
@ladvoc

ladvoc commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

I have temporarily disable Dart tests in CI since they are currently failing. In the interest of getting this merged, we can address in a follow-up PR.

@ladvoc
ladvoc merged commit 5067671 into main Jul 10, 2026
29 checks passed
@ladvoc
ladvoc deleted the ladvoc/data-tracks-uniffi branch July 10, 2026 18:26
pblazej added a commit that referenced this pull request Jul 30, 2026
…1300)

- build-android-*: cargo-make's `extend` replaces the parent env map
rather than merging it, so `env = { TARGET = ... }` silently dropped
ANDROID_RELEASE_FLAG and RUSTFLAGS — `--profile release` produced debug
.so files and android-copy-jniLibs found nothing in target/*/release.
Derive the flag from CARGO_MAKE_PROFILE in the script instead.
- android-bindgen-kotlin: TARGET leaks across cargo-make tasks, so
bindgen-kotlin's `build` dependency cross-compiled for Android with the
host linker ("cannot find -llog"). Pin TARGET back to the host triple
and keep symbols, which library-mode bindgen needs on Linux.
- Raise both size gates to measured values: ios-arm64 is 1088 KiB and
arm64-v8a 1174 KiB, having grown past the limits set in #1171 when the
data-track UniFFI surface landed in #1034.
- Check out inputs.tag_name in both reusable workflows;
workflow_dispatch was building the dispatch ref (main) rather than the
requested tag.

### Before you submit your PR

Make sure the following is true before submitting your PR:

- [ ] I have read the [contributing
guidelines](https://github.com/livekit/rust-sdks/blob/main/CONTRIBUTING.md)
and validated that this PR will be accepted.
- [ ] I have read and followed the principles regarding breaking
changes, testing, and code quality.

### PR description

Describe the changes in this PR. Explain what the PR is meant to solve
and how to reproduce the issue in the first place.

### Breaking changes

If this PR introduces breaking changes, list them here and document the
rationale for introducing such a change.

### MSRV

If the PR modifies the crate's MSRV (Minimum Supported Rust Version),
document it here.

### Testing

Ideally, unit test the code you add, but ensure you're not repeating
existing test cases. Use as many already written scaffolding, utilities
as possible; write your own, when needed. If external services, APIs,
tokens are required (e.g., running an LK server instance), provide the
necessary information. Make sure your tests perform useful,
context-aware assertions and do not simply emulate "happy paths".

### Async

We want the project to be runtime-agnostic, so please reuse what's
already in
[livekit-runtime](https://github.com/livekit/rust-sdks/blob/main/livekit-runtime/)
and feel free to add anything missing. It's ok to use Tokio directly,
when writing unit tests, if necessary. When testing, do not use
artificial delays for the state to "catch up"; instead, respect the
event flow and subscribe properly using channels or other mechanisms.

---------

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.

5 participants