Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .changes/uniffi-rust-core-dev-wiring
Original file line number Diff line number Diff line change
@@ -1 +1 @@
minor type="added" "Wire in the livekit_uniffi Rust core behind a native-only facade, delivered as a bundled cdylib via Native Assets"
minor type="added" "Wire in the livekit_ffi Rust core behind a native-only facade, delivered as a bundled cdylib via Native Assets"
14 changes: 7 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,27 +32,27 @@ CI (`build.yaml`) runs all of the above plus example-app builds for every platfo

Web/native divergence is handled with conditional imports (e.g. `track/processor_native.dart` vs `processor_web.dart`) — new platform-specific code should follow that pattern.

## The Rust core (`livekit_uniffi`)
## The Rust core (`livekit_ffi`)

`lib/src/uniffi/` wraps `livekit_uniffi`, a Dart package generated from the `livekit-uniffi` crate in the sibling `rust-sdks` repo. It reaches Rust through Dart's Native Assets: the package's `hook/build.dart` bundles a `cdylib` into the host app and the generated bindings call into it with `@Native`. This is why the SDK requires Flutter >= 3.38 / Dart >= 3.10.
`lib/src/ffi/` wraps `livekit_ffi`, a Dart package generated from the `livekit-ffi` crate in the sibling `rust-sdks` repo. It reaches Rust through Dart's Native Assets: the package's `hook/build.dart` bundles a `cdylib` into the host app and the generated bindings call into it with `@Native`. This is why the SDK requires Flutter >= 3.38 / Dart >= 3.10.

There is no dynamic library to load on the web, so `uniffi.dart` splits native/web the same way the rest of the SDK does. **`uniffi_io.dart` is the only file allowed to import `package:livekit_uniffi/...`** — importing it from anywhere reachable on web pulls `dart:ffi` into a web compile and breaks `flutter build web`/`--wasm`. Guard calls with `LiveKitUniffi.isAvailable`.
There is no dynamic library to load on the web, so `ffi.dart` splits native/web the same way the rest of the SDK does. **`ffi_io.dart` is the only file allowed to import `package:livekit_ffi/...`** — importing it from anywhere reachable on web pulls `dart:ffi` into a web compile and breaks `flutter build web`/`--wasm`. Guard calls with `LiveKitFFI.isAvailable`.

### Local development loop

`livekit_uniffi` is published on pub.dev and the normal dependency resolves it; its build hook downloads the matching prebuilt library from the `livekit-uniffi` GitHub release, so no Rust toolchain is needed. To develop against unreleased crate changes, point both `pubspec.yaml` and `example/pubspec.yaml` at a sibling `rust-sdks` checkout with a `dependency_overrides` entry (`path: ../rust-sdks/livekit-uniffi/packages/dart`, overrides do not propagate from a dependency, hence both) and produce or refresh the package with:
`livekit_ffi` is published on pub.dev and the normal dependency resolves it; its build hook downloads the matching prebuilt library from the `livekit-ffi` GitHub release, so no Rust toolchain is needed. To develop against unreleased crate changes, point both `pubspec.yaml` and `example/pubspec.yaml` at a sibling `rust-sdks` checkout with a `dependency_overrides` entry (`path: ../rust-sdks/livekit-ffi/packages/dart`, overrides do not propagate from a dependency, hence both) and produce or refresh the package with:

```sh
cd ../rust-sdks/livekit-uniffi
cd ../rust-sdks/livekit-ffi
cargo make dart-package # generates packages/dart/: bindings, pubspec, build hook, host cdylib
cd -
flutter pub get
flutter test test/uniffi/ # smoke test: calls buildVersion() across the FFI boundary
flutter test test/ffi/ # smoke test: calls buildVersion() across the FFI boundary
```

Requires `cargo-make`, `protoc` and `tera`. Re-run `cargo make dart-package` whenever the crate's exported surface changes — the build hook tracks the copied library, so a stale one won't be silently reused.

Two things to know about that hook: it picks the locally built library purely on target *OS*, not architecture, so a host build can be bundled into an iOS or Android build by mistake — verify the desktop target first when debugging. And its download mode (used when no local library is present) fetches `build-<triple>.zip` from the `livekit-uniffi` GitHub release matching the package version and verifies its SHA-256, so a version whose release lacks assets fails at build time rather than at runtime.
Two things to know about that hook: it picks the locally built library purely on target *OS*, not architecture, so a host build can be bundled into an iOS or Android build by mistake — verify the desktop target first when debugging. And its download mode (used when no local library is present) fetches `build-<triple>.zip` from the `livekit-ffi` GitHub release matching the package version and verifies its SHA-256, so a version whose release lacks assets fails at build time rather than at runtime.

## Common pitfalls (from issue history)

Expand Down
6 changes: 3 additions & 3 deletions example/lib/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import 'package:livekit_client/livekit_client.dart';

FutureOr<void> Function()? onWindowShouldClose;

/// One synchronous call into the Rust core through the uniffi facade. The
/// One synchronous call into the Rust core through the ffi facade. The
/// example shows the result at startup and on the connect page as a smoke
/// test that the native library was bundled and loads on this platform.
String rustCoreVersionLabel() {
if (!LiveKitUniffi.isAvailable) {
if (!LiveKitFFI.isAvailable) {
return 'Rust core not available on this platform';
}
try {
return 'Rust core ${LiveKitUniffi.buildVersion}';
return 'Rust core ${LiveKitFFI.buildVersion}';
} catch (error) {
return 'Rust core failed to load: $error';
}
Expand Down
2 changes: 1 addition & 1 deletion lib/livekit_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export 'src/token_source/custom.dart';
export 'src/token_source/caching.dart';
export 'src/token_source/development.dart';
export 'src/token_source/jwt.dart';
export 'src/uniffi/uniffi.dart';
export 'src/ffi/ffi.dart';

/// Misspelled alias for [kRpcVersion]. Kept for backwards compatibility with code
/// that referenced the original typo.
Expand Down
8 changes: 4 additions & 4 deletions lib/src/uniffi/uniffi.dart → lib/src/ffi/ffi.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@

import 'package:meta/meta.dart';

import 'uniffi_io.dart' if (dart.library.js_interop) 'uniffi_web.dart' as impl;
import 'ffi_io.dart' if (dart.library.js_interop) 'ffi_web.dart' as impl;

/// Facade over the Rust core exposed by the `livekit_uniffi` package.
/// Facade over the Rust core exposed by the `livekit_ffi` package.
///
/// `livekit_uniffi` reaches Rust through Dart's Native Assets: its build hook
/// `livekit_ffi` reaches Rust through Dart's Native Assets: its build hook
/// bundles a `cdylib` into the host app and the generated bindings call into it
/// with `@Native`. None of that exists on the web, where there is no dynamic
/// library to load, so every entry point here is split native/web through the
Expand All @@ -29,7 +29,7 @@ import 'uniffi_io.dart' if (dart.library.js_interop) 'uniffi_web.dart' as impl;
/// Callers get [isAvailable] to branch on, and platform-specific code paths
/// stay out of the public API surface.
@experimental
abstract final class LiveKitUniffi {
abstract final class LiveKitFFI {
/// Whether the Rust core can be called on this platform.
///
/// False on web. Every other member throws [UnsupportedError] when this is
Expand Down
8 changes: 4 additions & 4 deletions lib/src/uniffi/uniffi_io.dart → lib/src/ffi/ffi_io.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import 'package:livekit_uniffi/livekit_uniffi.dart' as uniffi;
import 'package:livekit_ffi/livekit_ffi.dart' as ffi;

/// Native implementation of [LiveKitUniffi]. See `uniffi.dart`.
/// Native implementation of [LiveKitFFI]. See `ffi.dart`.
///
/// This is the only file in the SDK that may import the generated bindings:
/// the conditional import in `uniffi.dart` keeps it out of web builds.
/// the conditional import in `ffi.dart` keeps it out of web builds.
const bool isAvailable = true;

String buildVersion() => uniffi.buildVersion();
String buildVersion() => ffi.buildVersion();
8 changes: 4 additions & 4 deletions lib/src/uniffi/uniffi_web.dart → lib/src/ffi/ffi_web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.

/// Web implementation of [LiveKitUniffi]. See `uniffi.dart`.
/// Web implementation of [LiveKitFFI]. See `ffi.dart`.
///
/// Native Assets bundles a `cdylib`, which the web has no way to load, so the
/// Rust core is simply absent here. This file deliberately does not import
/// `package:livekit_uniffi/...` -- doing so would pull `dart:ffi` into a web
/// `package:livekit_ffi/...` -- doing so would pull `dart:ffi` into a web
/// compile and fail the build.
const bool isAvailable = false;

Never buildVersion() => throw UnsupportedError(
'LiveKitUniffi.buildVersion is not available on web: the Rust core is '
'LiveKitFFI.buildVersion is not available on web: the Rust core is '
'delivered as a native library. Guard calls with '
'LiveKitUniffi.isAvailable.',
'LiveKitFFI.isAvailable.',
);
8 changes: 4 additions & 4 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -440,14 +440,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.1.0"
livekit_uniffi:
livekit_ffi:
dependency: "direct main"
description:
name: livekit_uniffi
sha256: "8ff1402ed684ce09899d77faeb828676030fff2ebf5449678484e665440c6e14"
name: livekit_ffi
sha256: "7b2a95698497b4c572c8da64588fc295e609aea235faf0552fc7e5c8c013c410"
url: "https://pub.dev"
source: hosted
version: "0.2.2"
version: "0.13.0"
logger:
dependency: transitive
description:
Expand Down
6 changes: 3 additions & 3 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ dependencies:
flutter_webrtc: 1.6.2+hotfix.4
dart_webrtc: ^1.8.0

# Rust core (livekit-uniffi), delivered as a bundled cdylib via Native Assets.
# Native platforms only, see lib/src/uniffi/.
livekit_uniffi: ^0.2.2
# Rust core (livekit-ffi), delivered as a bundled cdylib via Native Assets.
# Native platforms only, see lib/src/ffi/.
livekit_ffi: ^0.13.0

dev_dependencies:
flutter_test:
Expand Down
10 changes: 5 additions & 5 deletions test/uniffi/uniffi_test.dart → test/ffi/ffi_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,23 @@ library;

import 'package:flutter_test/flutter_test.dart';

import 'package:livekit_client/src/uniffi/uniffi.dart';
import 'package:livekit_client/src/ffi/ffi.dart';

void main() {
// Exercises the whole delivery chain rather than any particular API: the
// build hook resolved a cdylib for this target, Native Assets bundled it,
// `@Native` bound the symbol, and a value crossed back from Rust. If the
// bindgen or the hook regresses, this is what fails first.
group('livekit_uniffi', () {
group('livekit_ffi', () {
test('is available on native platforms', () {
expect(LiveKitUniffi.isAvailable, isTrue);
expect(LiveKitFFI.isAvailable, isTrue);
});

test('buildVersion returns the Rust core version', () {
final version = LiveKitUniffi.buildVersion;
final version = LiveKitFFI.buildVersion;
expect(version, isNotEmpty);
// The crate stamps its own semver, so assert the shape rather than a
// literal that would need bumping on every livekit-uniffi release.
// literal that would need bumping on every livekit-ffi release.
expect(version, matches(RegExp(r'^\d+\.\d+\.\d+')));
});
});
Expand Down
Loading