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

The fork's fix/multi-crate-framework branch was rebased onto cargo-swift main as a single
commit (open upstream as antoniusnaumann/cargo-swift#101) and no longer carries the
uniffi_bindgen 0.31.2 bump — that went to its own PR (antoniusnaumann/cargo-swift#102).
Re-pin the swift-xcframework task to the new revision.

Without that bindgen bump the generated callback-interface vtables are plain `static let`,
which the Swift 6 language mode rejects, so restore `swiftLanguageModes: [.v5]` in the
package templates until cargo-swift ships uniffi 0.31.2.

Give livekit-datatrack a uniffi.toml setting ffi_module_name = "RustLiveKitDataTrack" so its
generated FFI header matches the RustLiveKitUniFFI naming instead of the default
livekit_datatrackFFI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@pblazej

pblazej commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Wiring up the Swift SDK — hit two gaps in the binding surface for join/reconnect @ladvoc @1egoman:

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?

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).

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?

Query tracks is now used internally and not exported
@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
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