From 04c4db0b36a3444b8149f93ce9efd4f3b26decaa Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Thu, 9 Apr 2026 15:16:46 +0900 Subject: [PATCH 01/46] Add livekit-datatrack as dependency --- Cargo.lock | 1 + livekit-uniffi/Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index df7d10e1f..1c3975516 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4139,6 +4139,7 @@ name = "livekit-uniffi" version = "0.1.0" dependencies = [ "livekit-api", + "livekit-datatrack", "livekit-protocol", "log", "once_cell", diff --git a/livekit-uniffi/Cargo.toml b/livekit-uniffi/Cargo.toml index 8293512e9..597995c7e 100644 --- a/livekit-uniffi/Cargo.toml +++ b/livekit-uniffi/Cargo.toml @@ -14,6 +14,7 @@ publish = false [dependencies] livekit-protocol = { workspace = true } livekit-api = { workspace = true } +livekit-datatrack = { workspace = true } uniffi = { version = "0.30.0", features = ["cli", "scaffolding-ffi-buffer-fns"] } log = { workspace = true } tokio = { workspace = true, features = ["sync"] } From 95bf51c5b8e213c164ce4fa3b19667be7d4ce124 Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Thu, 9 Apr 2026 15:16:51 +0900 Subject: [PATCH 02/46] Create module --- livekit-uniffi/src/data_track/mod.rs | 14 ++++++++++++++ livekit-uniffi/src/lib.rs | 3 +++ 2 files changed, 17 insertions(+) create mode 100644 livekit-uniffi/src/data_track/mod.rs diff --git a/livekit-uniffi/src/data_track/mod.rs b/livekit-uniffi/src/data_track/mod.rs new file mode 100644 index 000000000..f263c3fea --- /dev/null +++ b/livekit-uniffi/src/data_track/mod.rs @@ -0,0 +1,14 @@ +// Copyright 2026 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + diff --git a/livekit-uniffi/src/lib.rs b/livekit-uniffi/src/lib.rs index 698bd4d0b..8792e7131 100644 --- a/livekit-uniffi/src/lib.rs +++ b/livekit-uniffi/src/lib.rs @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +/// Data tracks core from [`livekit-datatrack`]. +pub mod data_track; + /// Access token generation and verification from [`livekit-api::access_token`]. pub mod access_token; From 18cacdd0b5ff7c89ba11170695be1605a651ed30 Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Fri, 10 Apr 2026 14:26:38 +0900 Subject: [PATCH 03/46] Make public --- livekit-datatrack/src/local/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit-datatrack/src/local/mod.rs b/livekit-datatrack/src/local/mod.rs index 358547146..c47882065 100644 --- a/livekit-datatrack/src/local/mod.rs +++ b/livekit-datatrack/src/local/mod.rs @@ -152,7 +152,7 @@ impl Drop for LocalTrackInner { /// #[derive(Clone, Debug)] pub struct DataTrackOptions { - pub(crate) name: String, + pub name: String, } impl DataTrackOptions { From 0635e4b1401017cf0b37f6f7090b901ea335fabb Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Fri, 10 Apr 2026 14:26:51 +0900 Subject: [PATCH 04/46] Type alias, use error enum --- livekit-datatrack/src/e2ee.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/livekit-datatrack/src/e2ee.rs b/livekit-datatrack/src/e2ee.rs index 1f52c40ad..b286eb115 100644 --- a/livekit-datatrack/src/e2ee.rs +++ b/livekit-datatrack/src/e2ee.rs @@ -19,22 +19,25 @@ use thiserror::Error; // TODO: If a core module for end-to-end encryption is created in the future // (livekit-e2ee), these traits should be moved to there. +/// Twelve byte AES initialization vector (IV). +pub type InitializationVector = [u8; 12]; + /// Encrypted payload and metadata required for decryption. pub struct EncryptedPayload { pub payload: Bytes, - pub iv: [u8; 12], + pub iv: InitializationVector, pub key_index: u8, } /// An error indicating a payload could not be encrypted. #[derive(Debug, Error)] #[error("Encryption failed")] -pub struct EncryptionError; +pub enum EncryptionError {} /// An error indicating a payload could not be decrypted. #[derive(Debug, Error)] #[error("Decryption failed")] -pub struct DecryptionError; +pub enum DecryptionError {} /// Provider for encrypting payloads for E2EE. pub trait EncryptionProvider: Send + Sync + Debug { From 16791dd5ec814708837ba71fc778e173698a5f1e Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Fri, 10 Apr 2026 14:27:07 +0900 Subject: [PATCH 05/46] Add common module --- livekit-uniffi/src/common.rs | 17 +++++++++++++++++ livekit-uniffi/src/lib.rs | 2 ++ 2 files changed, 19 insertions(+) create mode 100644 livekit-uniffi/src/common.rs diff --git a/livekit-uniffi/src/common.rs b/livekit-uniffi/src/common.rs new file mode 100644 index 000000000..6ff66a67e --- /dev/null +++ b/livekit-uniffi/src/common.rs @@ -0,0 +1,17 @@ +// Copyright 2026 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use bytes::Bytes; + +uniffi::custom_type!(Bytes, Vec, { remote }); diff --git a/livekit-uniffi/src/lib.rs b/livekit-uniffi/src/lib.rs index 8792e7131..4ac01bf09 100644 --- a/livekit-uniffi/src/lib.rs +++ b/livekit-uniffi/src/lib.rs @@ -24,4 +24,6 @@ pub mod log_forward; /// Information about the build such as version. pub mod build_info; +pub mod common; + uniffi::setup_scaffolding!(); From 9b393299d9ec0fb4dbcc61b991b570fad21a323b Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Fri, 10 Apr 2026 14:27:20 +0900 Subject: [PATCH 06/46] Wip --- Cargo.lock | 4 + livekit-uniffi/Cargo.toml | 4 + livekit-uniffi/src/data_track/local.rs | 195 +++++++++++++++++++++++++ livekit-uniffi/src/data_track/mod.rs | 1 + 4 files changed, 204 insertions(+) create mode 100644 livekit-uniffi/src/data_track/local.rs diff --git a/Cargo.lock b/Cargo.lock index 1c3975516..5129c97de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4138,12 +4138,16 @@ dependencies = [ name = "livekit-uniffi" version = "0.1.0" dependencies = [ + "bytes", + "futures-util", "livekit-api", "livekit-datatrack", "livekit-protocol", "log", "once_cell", + "prost 0.12.6", "tokio", + "tokio-util", "uniffi", ] diff --git a/livekit-uniffi/Cargo.toml b/livekit-uniffi/Cargo.toml index 597995c7e..6b17385e0 100644 --- a/livekit-uniffi/Cargo.toml +++ b/livekit-uniffi/Cargo.toml @@ -18,6 +18,10 @@ livekit-datatrack = { workspace = true } uniffi = { version = "0.30.0", features = ["cli", "scaffolding-ffi-buffer-fns"] } log = { workspace = true } tokio = { workspace = true, features = ["sync"] } +tokio-util = "0.7.18" +prost = "0.12" +futures-util = { workspace = true, default-features = false, features = ["sink"] } +bytes = { workspace = true } once_cell = "1.21.3" [build-dependencies] diff --git a/livekit-uniffi/src/data_track/local.rs b/livekit-uniffi/src/data_track/local.rs new file mode 100644 index 000000000..75b531daa --- /dev/null +++ b/livekit-uniffi/src/data_track/local.rs @@ -0,0 +1,195 @@ +// Copyright 2026 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use bytes::Bytes; +use futures_util::{Stream, StreamExt}; +use inner::OutputEvent; +use livekit_datatrack::backend::EncryptionError; +use livekit_datatrack::{ + api::{DataTrackOptions, DataTrackSid, PublishError}, + backend::{local as inner, EncryptedPayload, InitializationVector}, +}; +use livekit_protocol as proto; +use prost::Message; +use std::sync::Arc; +use tokio_util::sync::{CancellationToken, DropGuard}; + +uniffi::custom_type!(DataTrackSid, String, { + remote, + lower: |s| String::from(s), + try_lift: |s| DataTrackSid::try_from(s).map_err(|e| uniffi::deps::anyhow::anyhow!("{e}")), +}); + +#[uniffi::remote(Record)] +pub struct DataTrackOptions { + pub name: String, +} + +/// Information about a published data track. +#[derive(uniffi::Record)] +pub struct DataTrackInfo { + pub sid: DataTrackSid, + pub name: String, + pub uses_e2ee: bool, +} + +#[uniffi::remote(Error)] +#[uniffi(flat_error)] +pub enum PublishError { + NotAllowed, + DuplicateName, + InvalidName, + Timeout, + LimitReached, + Disconnected, + Internal, +} + +#[derive(uniffi::Object)] +pub struct LocalDataTrack { + inner: livekit_datatrack::api::LocalDataTrack, +} + +impl From for LocalDataTrack { + fn from(inner: livekit_datatrack::api::LocalDataTrack) -> Self { + Self { inner } + } +} + +#[derive(uniffi::Object)] +struct LocalDataTrackManager { + input: inner::ManagerInput, + _drop_guard: DropGuard, +} + +#[uniffi::export] +impl LocalDataTrackManager { + #[uniffi::constructor] + pub fn new( + delegate: Arc, + e2ee_provider: Option>, + ) -> Arc { + let token = CancellationToken::new(); + + let manager_options = inner::ManagerOptions { encryption_provider: None }; + // TODO: encryption provider + + let (manager, input, output) = inner::Manager::new(manager_options); + tokio::spawn(manager.run()); + tokio::spawn(Self::delegate_forward_task(output, delegate, token.clone())); + + // TODO: send shutdown on drop + + Self { input, _drop_guard: token.drop_guard() }.into() + } +} + +impl LocalDataTrackManager { + async fn delegate_forward_task( + output: impl Stream, + delegate: Arc, + token: CancellationToken, + ) { + tokio::pin!(output); + loop { + tokio::select! { + _ = token.cancelled() => break, + Some(event) = output.next() => Self::forward_event(event, &delegate) + } + } + } + + fn forward_event(event: OutputEvent, delegate: &Arc) { + match event { + OutputEvent::PacketsAvailable(packets) => delegate.on_packets_available(packets), + OutputEvent::SfuPublishRequest(req) => { + let req = proto::signal_request::Message::PublishDataTrackRequest(req.into()); + Self::forward_signal_request(req, &delegate); + } + OutputEvent::SfuUnpublishRequest(req) => { + let req = proto::signal_request::Message::UnpublishDataTrackRequest(req.into()); + Self::forward_signal_request(req, &delegate); + } + } + } + + fn forward_signal_request( + message: proto::signal_request::Message, + delegate: &Arc, + ) { + let req = proto::SignalRequest { message: Some(message) }.encode_to_vec(); + delegate.on_signal_request(req); + } +} + +#[uniffi::export] +impl LocalDataTrackManager { + pub async fn publish_track( + &self, + options: DataTrackOptions, + ) -> Result { + self.input.publish_track(options).await.map(|track| track.into()) + } + + pub async fn query_tracks(&self) -> Vec { + self.input + .query_tracks() + .await + .into_iter() + .map(|info| DataTrackInfo { + sid: info.sid(), + name: info.name().to_string(), + uses_e2ee: info.uses_e2ee(), + }) + .collect() + } + + pub async fn republish_tracks(&self) { + _ = self.input.send(inner::InputEvent::RepublishTracks); + } +} + +/// Delegate for receiving output events from [`LocalDataTrackManager`]. +#[uniffi::export(with_foreign)] +pub trait LocalDataTrackManagerDelegate: Send + Sync { + /// Encoded signal request to be forwarded to the SFU. + fn on_signal_request(&self, request: Vec); + + /// Packets available to be sent over the data channel transport. + fn on_packets_available(&self, packets: Vec); +} + +uniffi::custom_type!(InitializationVector, Vec, { + remote, + lower: |iv| iv.to_vec(), + try_lift: |v| v.try_into() + .map_err(|_| uniffi::deps::anyhow::anyhow!("IV must be exactly 12 bytes")) +}); + +#[uniffi::remote(Record)] +pub struct EncryptedPayload { + pub payload: Bytes, + pub iv: InitializationVector, + pub key_index: u8, +} + +#[uniffi::remote(Error)] +#[uniffi(flat_error)] +pub enum EncryptionError {} + +#[uniffi::export(with_foreign)] +pub trait LocalDataTrackEncryptionProvider: Send + Sync { + /// Encrypts the given payload being sent by the local participant. + fn encrypt(&self, payload: Bytes) -> Result; +} diff --git a/livekit-uniffi/src/data_track/mod.rs b/livekit-uniffi/src/data_track/mod.rs index f263c3fea..06c0418ab 100644 --- a/livekit-uniffi/src/data_track/mod.rs +++ b/livekit-uniffi/src/data_track/mod.rs @@ -12,3 +12,4 @@ // See the License for the specific language governing permissions and // limitations under the License. +pub mod local; \ No newline at end of file From 0c621f9be244833fbc689c1e5bf91c22d0d920a7 Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Fri, 10 Apr 2026 15:19:47 +0900 Subject: [PATCH 07/46] Wip --- livekit-uniffi/src/data_track/local.rs | 66 ++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/livekit-uniffi/src/data_track/local.rs b/livekit-uniffi/src/data_track/local.rs index 75b531daa..810613230 100644 --- a/livekit-uniffi/src/data_track/local.rs +++ b/livekit-uniffi/src/data_track/local.rs @@ -15,6 +15,9 @@ use bytes::Bytes; use futures_util::{Stream, StreamExt}; use inner::OutputEvent; +use livekit_datatrack::backend::local::{ + publish_result_from_request_response, InputEvent, ManagerInput, SfuPublishResponse, +}; use livekit_datatrack::backend::EncryptionError; use livekit_datatrack::{ api::{DataTrackOptions, DataTrackSid, PublishError}, @@ -67,6 +70,7 @@ impl From for LocalDataTrack { } } +/// System for managing data track publications. #[derive(uniffi::Object)] struct LocalDataTrackManager { input: inner::ManagerInput, @@ -86,16 +90,21 @@ impl LocalDataTrackManager { // TODO: encryption provider let (manager, input, output) = inner::Manager::new(manager_options); - tokio::spawn(manager.run()); + tokio::spawn(Self::shutdown_forward_task(input.clone(), token.clone())); tokio::spawn(Self::delegate_forward_task(output, delegate, token.clone())); - - // TODO: send shutdown on drop + tokio::spawn(manager.run()); Self { input, _drop_guard: token.drop_guard() }.into() } } impl LocalDataTrackManager { + async fn shutdown_forward_task(input: ManagerInput, token: CancellationToken) { + // TODO: consider having manager work with cancellation token out-of-the-box. + token.cancelled().await; + _ = input.send(InputEvent::Shutdown); + } + async fn delegate_forward_task( output: impl Stream, delegate: Arc, @@ -135,6 +144,7 @@ impl LocalDataTrackManager { #[uniffi::export] impl LocalDataTrackManager { + /// Publishes a data track with given options. pub async fn publish_track( &self, options: DataTrackOptions, @@ -142,6 +152,10 @@ impl LocalDataTrackManager { self.input.publish_track(options).await.map(|track| track.into()) } + /// Get information about all currently published tracks. + /// + /// This does not include publications that are still pending. + /// pub async fn query_tracks(&self) -> Vec { self.input .query_tracks() @@ -155,9 +169,55 @@ impl LocalDataTrackManager { .collect() } + /// Republish all tracks. + /// + /// This must be invoked after a full reconnect in order for existing publications + /// to be recognized by the SFU. Each republished track will be assigned a new SID. + /// pub async fn republish_tracks(&self) { _ = self.input.send(inner::InputEvent::RepublishTracks); } + + /// Handles a serialized signal response from the SFU. + /// + /// This must be invoked for the following response types in order for the + /// manager to function properly: + /// + /// - `RequestResponse` + /// - `PublishDataTrackResponse` + /// + /// Invoking for other response types not listed above will log an error. + /// + pub fn handle_signal_response(&self, res: &[u8]) { + // TODO: consider returning a result + let Ok(Some(msg)) = proto::SignalResponse::decode(res).map(|res| res.message) else { + log::error!("Failed to decode signal response"); + return; + }; + + use proto::signal_response::Message; + let publish_res = match msg { + Message::RequestResponse(msg) => { + let Some(res) = publish_result_from_request_response(&msg) else { + // Not from data track publish request. + return; + }; + res + } + Message::PublishDataTrackResponse(res) => { + // TODO: handle error + let res: SfuPublishResponse = res.try_into().unwrap(); + res + } + _ => { + log::error!("Unsupported signal response type"); + return; + } + }; + + let event: InputEvent = publish_res.into(); + _ = self.input.send(event); + } } /// Delegate for receiving output events from [`LocalDataTrackManager`]. From 5943438d740b036b7a2595654f7381c33b616a9f Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Fri, 10 Apr 2026 18:17:12 +0900 Subject: [PATCH 08/46] Make fields public --- livekit-datatrack/src/frame.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/livekit-datatrack/src/frame.rs b/livekit-datatrack/src/frame.rs index cfa792afa..e84276d60 100644 --- a/livekit-datatrack/src/frame.rs +++ b/livekit-datatrack/src/frame.rs @@ -32,8 +32,8 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; /// #[derive(Clone, Default)] pub struct DataTrackFrame { - pub(crate) payload: Bytes, - pub(crate) user_timestamp: Option, + pub payload: Bytes, + pub user_timestamp: Option, } impl DataTrackFrame { From 895b0345050c7672aa7589639c4c1963de3ff343 Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Fri, 10 Apr 2026 18:17:22 +0900 Subject: [PATCH 09/46] Clarify docs --- livekit-datatrack/src/track.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit-datatrack/src/track.rs b/livekit-datatrack/src/track.rs index 8c313b2b1..3d5e3cd03 100644 --- a/livekit-datatrack/src/track.rs +++ b/livekit-datatrack/src/track.rs @@ -42,7 +42,7 @@ impl DataTrack { &self.info } - /// Whether or not the track is still published. + /// Whether or not the track is currently published. pub fn is_published(&self) -> bool { match self.inner.as_ref() { DataTrackInner::Local(inner) => inner.is_published(), From 9b275defe35b00e8bd8b10a346eb25f2872ada62 Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Fri, 10 Apr 2026 18:18:15 +0900 Subject: [PATCH 10/46] Expose local track methods --- livekit-uniffi/src/data_track/local.rs | 50 +++++++++++++++++--------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/livekit-uniffi/src/data_track/local.rs b/livekit-uniffi/src/data_track/local.rs index 810613230..d03c03473 100644 --- a/livekit-uniffi/src/data_track/local.rs +++ b/livekit-uniffi/src/data_track/local.rs @@ -15,6 +15,7 @@ use bytes::Bytes; use futures_util::{Stream, StreamExt}; use inner::OutputEvent; +use livekit_datatrack::api::{DataTrack, DataTrackFrame, Local, PushFrameErrorReason}; use livekit_datatrack::backend::local::{ publish_result_from_request_response, InputEvent, ManagerInput, SfuPublishResponse, }; @@ -47,6 +48,18 @@ pub struct DataTrackInfo { pub uses_e2ee: bool, } +impl From<&livekit_datatrack::api::DataTrackInfo> for DataTrackInfo { + fn from(info: &livekit_datatrack::api::DataTrackInfo) -> Self { + Self { sid: info.sid(), name: info.name().to_string(), uses_e2ee: info.uses_e2ee() } + } +} + +#[uniffi::remote(Error)] +pub enum PushFrameErrorReason { + TrackUnpublished, + QueueFull, +} + #[uniffi::remote(Error)] #[uniffi(flat_error)] pub enum PublishError { @@ -60,13 +73,25 @@ pub enum PublishError { } #[derive(uniffi::Object)] -pub struct LocalDataTrack { - inner: livekit_datatrack::api::LocalDataTrack, -} +pub struct LocalDataTrack(DataTrack); + +#[uniffi::export] +impl LocalDataTrack { + /// Whether or not the track is currently published. + fn is_published(&self) -> bool { + self.0.is_published() + } + + /// Information about the data track. + fn info(&self) -> DataTrackInfo { + self.0.info().into() + } -impl From for LocalDataTrack { - fn from(inner: livekit_datatrack::api::LocalDataTrack) -> Self { - Self { inner } + /// Try pushing a frame to subscribers of the track. + fn try_push(&self, frame: DataTrackFrame) -> Result<(), PushFrameErrorReason> { + // `PushFrameError` returns ownership of the unpublished frame to the caller; + // since this not applicable in an FFI context, just provide the reason. + self.0.try_push(frame).map_err(|err| err.reason()) } } @@ -149,7 +174,7 @@ impl LocalDataTrackManager { &self, options: DataTrackOptions, ) -> Result { - self.input.publish_track(options).await.map(|track| track.into()) + self.input.publish_track(options).await.map(|track| LocalDataTrack(track)) } /// Get information about all currently published tracks. @@ -157,16 +182,7 @@ impl LocalDataTrackManager { /// This does not include publications that are still pending. /// pub async fn query_tracks(&self) -> Vec { - self.input - .query_tracks() - .await - .into_iter() - .map(|info| DataTrackInfo { - sid: info.sid(), - name: info.name().to_string(), - uses_e2ee: info.uses_e2ee(), - }) - .collect() + self.input.query_tracks().await.into_iter().map(|info| info.as_ref().into()).collect() } /// Republish all tracks. From d254497e0fa2e466aca6e0adf5d6005959532806 Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Fri, 10 Apr 2026 18:22:01 +0900 Subject: [PATCH 11/46] Move common types to top level mod --- livekit-uniffi/src/data_track/local.rs | 23 ++------------------ livekit-uniffi/src/data_track/mod.rs | 30 +++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/livekit-uniffi/src/data_track/local.rs b/livekit-uniffi/src/data_track/local.rs index d03c03473..c5236f8dc 100644 --- a/livekit-uniffi/src/data_track/local.rs +++ b/livekit-uniffi/src/data_track/local.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use super::DataTrackInfo; use bytes::Bytes; use futures_util::{Stream, StreamExt}; use inner::OutputEvent; @@ -21,7 +22,7 @@ use livekit_datatrack::backend::local::{ }; use livekit_datatrack::backend::EncryptionError; use livekit_datatrack::{ - api::{DataTrackOptions, DataTrackSid, PublishError}, + api::{DataTrackOptions, PublishError}, backend::{local as inner, EncryptedPayload, InitializationVector}, }; use livekit_protocol as proto; @@ -29,31 +30,11 @@ use prost::Message; use std::sync::Arc; use tokio_util::sync::{CancellationToken, DropGuard}; -uniffi::custom_type!(DataTrackSid, String, { - remote, - lower: |s| String::from(s), - try_lift: |s| DataTrackSid::try_from(s).map_err(|e| uniffi::deps::anyhow::anyhow!("{e}")), -}); - #[uniffi::remote(Record)] pub struct DataTrackOptions { pub name: String, } -/// Information about a published data track. -#[derive(uniffi::Record)] -pub struct DataTrackInfo { - pub sid: DataTrackSid, - pub name: String, - pub uses_e2ee: bool, -} - -impl From<&livekit_datatrack::api::DataTrackInfo> for DataTrackInfo { - fn from(info: &livekit_datatrack::api::DataTrackInfo) -> Self { - Self { sid: info.sid(), name: info.name().to_string(), uses_e2ee: info.uses_e2ee() } - } -} - #[uniffi::remote(Error)] pub enum PushFrameErrorReason { TrackUnpublished, diff --git a/livekit-uniffi/src/data_track/mod.rs b/livekit-uniffi/src/data_track/mod.rs index 06c0418ab..2cd5c5f4d 100644 --- a/livekit-uniffi/src/data_track/mod.rs +++ b/livekit-uniffi/src/data_track/mod.rs @@ -12,4 +12,32 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub mod local; \ No newline at end of file +use bytes::Bytes; +use livekit_datatrack::api::{DataTrackFrame, DataTrackSid}; + +pub mod local; + +uniffi::custom_type!(DataTrackSid, String, { + remote, + lower: |s| String::from(s), + try_lift: |s| DataTrackSid::try_from(s).map_err(|e| uniffi::deps::anyhow::anyhow!("{e}")), +}); + +#[uniffi::remote(Record)] +pub struct DataTrackFrame { + payload: Bytes, + user_timestamp: Option +} +/// Information about a published data track. +#[derive(uniffi::Record)] +pub struct DataTrackInfo { + pub sid: DataTrackSid, + pub name: String, + pub uses_e2ee: bool, +} + +impl From<&livekit_datatrack::api::DataTrackInfo> for DataTrackInfo { + fn from(info: &livekit_datatrack::api::DataTrackInfo) -> Self { + Self { sid: info.sid(), name: info.name().to_string(), uses_e2ee: info.uses_e2ee() } + } +} From e275186b000609acdc0d45b5b926f98c1989658c Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Fri, 10 Apr 2026 18:40:08 +0900 Subject: [PATCH 12/46] Support encryption provider --- livekit-uniffi/src/data_track/local.rs | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/livekit-uniffi/src/data_track/local.rs b/livekit-uniffi/src/data_track/local.rs index c5236f8dc..99ef0ea18 100644 --- a/livekit-uniffi/src/data_track/local.rs +++ b/livekit-uniffi/src/data_track/local.rs @@ -23,10 +23,11 @@ use livekit_datatrack::backend::local::{ use livekit_datatrack::backend::EncryptionError; use livekit_datatrack::{ api::{DataTrackOptions, PublishError}, - backend::{local as inner, EncryptedPayload, InitializationVector}, + backend::{local as inner, EncryptedPayload, EncryptionProvider, InitializationVector}, }; use livekit_protocol as proto; use prost::Message; +use std::fmt; use std::sync::Arc; use tokio_util::sync::{CancellationToken, DropGuard}; @@ -88,12 +89,13 @@ impl LocalDataTrackManager { #[uniffi::constructor] pub fn new( delegate: Arc, - e2ee_provider: Option>, + encryption_provider: Option>, ) -> Arc { let token = CancellationToken::new(); - let manager_options = inner::ManagerOptions { encryption_provider: None }; - // TODO: encryption provider + let encryption_provider = encryption_provider + .map(|p| Arc::new(FfiEncryptionProvider(p)) as Arc); + let manager_options = inner::ManagerOptions { encryption_provider }; let (manager, input, output) = inner::Manager::new(manager_options); tokio::spawn(Self::shutdown_forward_task(input.clone(), token.clone())); @@ -250,3 +252,18 @@ pub trait LocalDataTrackEncryptionProvider: Send + Sync { /// Encrypts the given payload being sent by the local participant. fn encrypt(&self, payload: Bytes) -> Result; } + +/// Adapts [`LocalDataTrackEncryptionProvider`] to implement [`EncryptionProvider`]. +struct FfiEncryptionProvider(Arc); + +impl EncryptionProvider for FfiEncryptionProvider { + fn encrypt(&self, payload: Bytes) -> Result { + self.0.encrypt(payload) + } +} + +impl fmt::Debug for FfiEncryptionProvider { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FfiEncryptionProvider").finish_non_exhaustive() + } +} From 2e91900bd037ae7f99327d362a2bf7a7f32cf587 Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Fri, 10 Apr 2026 18:52:24 +0900 Subject: [PATCH 13/46] Docs --- livekit-uniffi/src/data_track/local.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/livekit-uniffi/src/data_track/local.rs b/livekit-uniffi/src/data_track/local.rs index 99ef0ea18..8ff66d7da 100644 --- a/livekit-uniffi/src/data_track/local.rs +++ b/livekit-uniffi/src/data_track/local.rs @@ -54,6 +54,7 @@ pub enum PublishError { Internal, } +/// Data track published by the local participant. #[derive(uniffi::Object)] pub struct LocalDataTrack(DataTrack); @@ -72,12 +73,17 @@ impl LocalDataTrack { /// Try pushing a frame to subscribers of the track. fn try_push(&self, frame: DataTrackFrame) -> Result<(), PushFrameErrorReason> { // `PushFrameError` returns ownership of the unpublished frame to the caller; - // since this not applicable in an FFI context, just provide the reason. + // since this isn't applicable in an FFI context, just provide the reason. self.0.try_push(frame).map_err(|err| err.reason()) } } /// System for managing data track publications. +/// +/// FFI clients construct an instance of this inside `Room`, +/// send it relevant signal responses via [`Self::handle_signal_response`], +/// and handle outputs via [`LocalDataTrackManagerDelegate`]. +/// #[derive(uniffi::Object)] struct LocalDataTrackManager { input: inner::ManagerInput, @@ -247,6 +253,7 @@ pub struct EncryptedPayload { #[uniffi(flat_error)] pub enum EncryptionError {} +/// Provider for encrypting payloads for E2EE. #[uniffi::export(with_foreign)] pub trait LocalDataTrackEncryptionProvider: Send + Sync { /// Encrypts the given payload being sent by the local participant. From af84b2277bdfad629a380353f7e97fc561871baf Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Tue, 21 Apr 2026 12:01:45 +0900 Subject: [PATCH 14/46] Apply Clippy --- livekit-uniffi/src/data_track/local.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/livekit-uniffi/src/data_track/local.rs b/livekit-uniffi/src/data_track/local.rs index 8ff66d7da..b7d4c880d 100644 --- a/livekit-uniffi/src/data_track/local.rs +++ b/livekit-uniffi/src/data_track/local.rs @@ -138,11 +138,11 @@ impl LocalDataTrackManager { OutputEvent::PacketsAvailable(packets) => delegate.on_packets_available(packets), OutputEvent::SfuPublishRequest(req) => { let req = proto::signal_request::Message::PublishDataTrackRequest(req.into()); - Self::forward_signal_request(req, &delegate); + Self::forward_signal_request(req, delegate); } OutputEvent::SfuUnpublishRequest(req) => { let req = proto::signal_request::Message::UnpublishDataTrackRequest(req.into()); - Self::forward_signal_request(req, &delegate); + Self::forward_signal_request(req, delegate); } } } @@ -163,7 +163,7 @@ impl LocalDataTrackManager { &self, options: DataTrackOptions, ) -> Result { - self.input.publish_track(options).await.map(|track| LocalDataTrack(track)) + self.input.publish_track(options).await.map(LocalDataTrack) } /// Get information about all currently published tracks. From 22d3a357c7180d0021d4aa905773b69c3528613b Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Tue, 21 Apr 2026 12:05:24 +0900 Subject: [PATCH 15/46] Expose unpublish --- livekit-uniffi/src/data_track/local.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/livekit-uniffi/src/data_track/local.rs b/livekit-uniffi/src/data_track/local.rs index b7d4c880d..1ac26cab8 100644 --- a/livekit-uniffi/src/data_track/local.rs +++ b/livekit-uniffi/src/data_track/local.rs @@ -76,6 +76,11 @@ impl LocalDataTrack { // since this isn't applicable in an FFI context, just provide the reason. self.0.try_push(frame).map_err(|err| err.reason()) } + + /// Unpublishes the track. + fn unpublish(&self) { + self.0.unpublish(); + } } /// System for managing data track publications. From 00a6e9916f6743f244c772b47cb50d83630763c2 Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Tue, 21 Apr 2026 13:15:44 +0900 Subject: [PATCH 16/46] Remote track support --- livekit-uniffi/src/data_track/mod.rs | 1 + livekit-uniffi/src/data_track/remote.rs | 267 ++++++++++++++++++++++++ 2 files changed, 268 insertions(+) create mode 100644 livekit-uniffi/src/data_track/remote.rs diff --git a/livekit-uniffi/src/data_track/mod.rs b/livekit-uniffi/src/data_track/mod.rs index 2cd5c5f4d..6c248fa91 100644 --- a/livekit-uniffi/src/data_track/mod.rs +++ b/livekit-uniffi/src/data_track/mod.rs @@ -16,6 +16,7 @@ use bytes::Bytes; use livekit_datatrack::api::{DataTrackFrame, DataTrackSid}; pub mod local; +pub mod remote; uniffi::custom_type!(DataTrackSid, String, { remote, diff --git a/livekit-uniffi/src/data_track/remote.rs b/livekit-uniffi/src/data_track/remote.rs new file mode 100644 index 000000000..6fe0b30ca --- /dev/null +++ b/livekit-uniffi/src/data_track/remote.rs @@ -0,0 +1,267 @@ +// Copyright 2026 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::data_track::DataTrackInfo; +use bytes::Bytes; +use core::fmt; +use futures_util::{Stream, StreamExt}; +use livekit_datatrack::{ + api::{DataTrack, DataTrackFrame, DataTrackSid, DataTrackSubscribeError, Remote}, + backend::{ + remote::{self as inner, event_from_participant_update}, + DecryptionError, DecryptionProvider, EncryptedPayload, + }, +}; +use livekit_protocol as proto; +use prost::Message; +use std::sync::Arc; +use tokio::sync::Mutex; +use tokio_util::sync::{CancellationToken, DropGuard}; + +#[uniffi::remote(Error)] +#[uniffi(flat_error)] +pub enum DataTrackSubscribeError { + Unpublished, + Timeout, + Disconnected, + Internal, +} + +/// Data track published by the local participant. +#[derive(uniffi::Object)] +pub struct RemoteDataTrack(DataTrack); + +#[uniffi::export] +impl RemoteDataTrack { + /// Whether or not the track is currently published. + fn is_published(&self) -> bool { + self.0.is_published() + } + + /// Information about the data track. + fn info(&self) -> DataTrackInfo { + self.0.info().into() + } + + /// Identity of the participant who published the track. + fn publisher_identity(&self) -> String { + self.0.publisher_identity().to_string() + } + + /// Subscribes to the data track. + async fn subscribe(&self) -> Result { + self.0.subscribe().await.map(|stream| DataTrackStream(Mutex::new(stream))) + } +} + +#[derive(uniffi::Object)] +struct DataTrackStream(Mutex); + +#[uniffi::export] +impl DataTrackStream { + async fn next(&self) -> Option { + // TODO: avoid mutex? + self.0.try_lock().unwrap().next().await + } +} + +/// System for managing data track subscriptions. +#[derive(uniffi::Object)] +struct RemoteDataTrackManager { + input: inner::ManagerInput, + _drop_guard: DropGuard, +} + +#[uniffi::export] +impl RemoteDataTrackManager { + #[uniffi::constructor] + pub fn new( + delegate: Arc, + decryption_provider: Option>, + ) -> Arc { + let token = CancellationToken::new(); + + let decryption_provider = decryption_provider + .map(|p| Arc::new(FfiDecryptionProvider(p)) as Arc); + let manager_options = inner::ManagerOptions { decryption_provider }; + + let (manager, input, output) = inner::Manager::new(manager_options); + tokio::spawn(Self::shutdown_forward_task(input.clone(), token.clone())); + tokio::spawn(Self::delegate_forward_task(output, delegate, token.clone())); + tokio::spawn(manager.run()); + + Self { input, _drop_guard: token.drop_guard() }.into() + } +} + +impl RemoteDataTrackManager { + async fn shutdown_forward_task(input: inner::ManagerInput, token: CancellationToken) { + // TODO: consider having manager work with cancellation token out-of-the-box. + token.cancelled().await; + _ = input.send(inner::InputEvent::Shutdown); + } + + async fn delegate_forward_task( + output: impl Stream, + delegate: Arc, + token: CancellationToken, + ) { + tokio::pin!(output); + loop { + tokio::select! { + _ = token.cancelled() => break, + Some(event) = output.next() => Self::forward_event(event, &delegate) + } + } + } + + fn forward_event( + event: inner::OutputEvent, + delegate: &Arc, + ) { + match event { + inner::OutputEvent::SfuUpdateSubscription(req) => { + let req = proto::signal_request::Message::UpdateDataSubscription(req.into()); + Self::forward_signal_request(req, delegate); + } + inner::OutputEvent::TrackPublished(event) => { + let track = Arc::new(RemoteDataTrack(event.track)); + delegate.on_track_published(track); + } + inner::OutputEvent::TrackUnpublished(event) => delegate.on_track_unpublished(event.sid), + } + } + + fn forward_signal_request( + message: proto::signal_request::Message, + delegate: &Arc, + ) { + let req = proto::SignalRequest { message: Some(message) }.encode_to_vec(); + delegate.on_signal_request(req); + } +} + +#[uniffi::export] +impl RemoteDataTrackManager { + /// Resend all subscription updates. + /// + /// This must be sent after a full reconnect to ensure the SFU knows which tracks + /// are subscribed to locally. + /// + pub fn resend_subscription_updates(&self) { + _ = self.input.send(inner::InputEvent::ResendSubscriptionUpdates); + } + + /// Handles a serialized signal response from the SFU. + /// + /// This must be invoked for the following response types in order for the + /// manager to function properly: + /// + /// - `ParticipantUpdate` + /// - `DataTrackSubscriberHandles` + /// + /// Invoking for other response types not listed above will log an error. + /// + /// Note: the local participant identity is required to exclude data tracks published by the + /// local participant from being treated as remote tracks. + /// + pub fn handle_signal_response(&self, res: &[u8], local_participant_identity: String) { + // TODO: consider returning a result + let Ok(Some(msg)) = proto::SignalResponse::decode(res).map(|res| res.message) else { + log::error!("Failed to decode signal response"); + return; + }; + use proto::signal_response::Message; + match msg { + Message::Update(mut msg) => { + let Some(event) = + event_from_participant_update(&mut msg, &local_participant_identity) + .inspect_err(|err| log::error!("{err}")) + .ok() + else { + return; + }; + _ = self.input.send(event.into()); + } + Message::DataTrackSubscriberHandles(msg) => { + let event: inner::SfuSubscriberHandles = msg.try_into().unwrap(); // TODO: handle error + _ = self.input.send(event.into()) + } + _ => { + log::error!("Unsupported signal response type"); + } + }; + } + + /// Handles a encoded packet received over the data channel. + pub fn handle_packet_received(&self, packet: Bytes) { + _ = self.input.send(inner::InputEvent::PacketReceived(packet)) + } +} + +/// Delegate for receiving output events from [`RemoteDataTrackManager`]. +#[uniffi::export(with_foreign)] +pub trait RemoteDataTrackManagerDelegate: Send + Sync { + /// Encoded signal request to be forwarded to the SFU. + fn on_signal_request(&self, request: Vec); + + /// A track has been published by a remote participant and is available to be + /// subscribed to. + /// + /// Emit a public event to deliver the track to the user, allowing them to subscribe + /// with [`RemoteDataTrack::subscribe`] if desired. + /// + fn on_track_published(&self, track: Arc); + + /// A track with the given SID has been unpublished by a remote participant. + fn on_track_unpublished(&self, sid: DataTrackSid); +} + +#[uniffi::remote(Error)] +#[uniffi(flat_error)] +pub enum DecryptionError {} + +/// Provider for decrypting payloads for E2EE. +#[uniffi::export(with_foreign)] +pub trait RemoteDataTrackDecryptionProvider: Send + Sync { + /// Decrypts the given payload received from a remote participant. + /// + /// Sender identity is required in order for the proper key to be used + /// for decryption. + /// + fn decrypt( + &self, + payload: EncryptedPayload, + sender_identity: String, + ) -> Result; +} + +/// Adapts [`RemoteDataTrackDecryptionProvider`] to implement [`DecryptionProvider`]. +struct FfiDecryptionProvider(Arc); + +impl DecryptionProvider for FfiDecryptionProvider { + fn decrypt( + &self, + payload: EncryptedPayload, + sender_identity: &str, + ) -> Result { + self.0.decrypt(payload, sender_identity.to_string()) + } +} + +impl fmt::Debug for FfiDecryptionProvider { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FfiDecryptionProvider").finish_non_exhaustive() + } +} From 40b8cc3e2b6ad5d23062471f8c8597b737cb23bc Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Tue, 21 Apr 2026 13:40:38 +0900 Subject: [PATCH 17/46] Error handling for signal response --- livekit-uniffi/src/data_track/local.rs | 24 ++++++++--------- livekit-uniffi/src/data_track/mod.rs | 14 ++++++++++ livekit-uniffi/src/data_track/remote.rs | 35 ++++++++++++------------- 3 files changed, 42 insertions(+), 31 deletions(-) diff --git a/livekit-uniffi/src/data_track/local.rs b/livekit-uniffi/src/data_track/local.rs index 1ac26cab8..362eab743 100644 --- a/livekit-uniffi/src/data_track/local.rs +++ b/livekit-uniffi/src/data_track/local.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::data_track::DataTrackSignalResponseError; use super::DataTrackInfo; use bytes::Bytes; use futures_util::{Stream, StreamExt}; @@ -196,21 +197,19 @@ impl LocalDataTrackManager { /// - `RequestResponse` /// - `PublishDataTrackResponse` /// - /// Invoking for other response types not listed above will log an error. - /// - pub fn handle_signal_response(&self, res: &[u8]) { - // TODO: consider returning a result - let Ok(Some(msg)) = proto::SignalResponse::decode(res).map(|res| res.message) else { - log::error!("Failed to decode signal response"); - return; - }; + pub fn handle_signal_response(&self, res: &[u8]) -> Result<(), DataTrackSignalResponseError> { + + let res = proto::SignalResponse::decode(res) + .map_err(|err| DataTrackSignalResponseError::Decode(err))?; + + let msg = res.message.ok_or(DataTrackSignalResponseError::EmptyMessage)?; use proto::signal_response::Message; let publish_res = match msg { Message::RequestResponse(msg) => { let Some(res) = publish_result_from_request_response(&msg) else { // Not from data track publish request. - return; + return Ok(()); }; res } @@ -219,14 +218,13 @@ impl LocalDataTrackManager { let res: SfuPublishResponse = res.try_into().unwrap(); res } - _ => { - log::error!("Unsupported signal response type"); - return; - } + _ => return Err(DataTrackSignalResponseError::UnsupportedType), }; let event: InputEvent = publish_res.into(); _ = self.input.send(event); + + Ok(()) } } diff --git a/livekit-uniffi/src/data_track/mod.rs b/livekit-uniffi/src/data_track/mod.rs index 6c248fa91..e5007b12b 100644 --- a/livekit-uniffi/src/data_track/mod.rs +++ b/livekit-uniffi/src/data_track/mod.rs @@ -42,3 +42,17 @@ impl From<&livekit_datatrack::api::DataTrackInfo> for DataTrackInfo { Self { sid: info.sid(), name: info.name().to_string(), uses_e2ee: info.uses_e2ee() } } } + +/// Signal response could not be handled. +#[derive(uniffi::Error, thiserror::Error, Debug)] +#[uniffi(flat_error)] +pub enum DataTrackSignalResponseError { + #[error("Response decoding failed: {0}")] + Decode(prost::DecodeError), + #[error("Response container has no message")] + EmptyMessage, + #[error("Unsupported response type in this context")] + UnsupportedType, + #[error(transparent)] + Internal(livekit_datatrack::api::InternalError) +} \ No newline at end of file diff --git a/livekit-uniffi/src/data_track/remote.rs b/livekit-uniffi/src/data_track/remote.rs index 6fe0b30ca..dbaaab4b8 100644 --- a/livekit-uniffi/src/data_track/remote.rs +++ b/livekit-uniffi/src/data_track/remote.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::data_track::DataTrackInfo; +use crate::data_track::{DataTrackInfo, DataTrackSignalResponseError}; use bytes::Bytes; use core::fmt; use futures_util::{Stream, StreamExt}; @@ -171,37 +171,36 @@ impl RemoteDataTrackManager { /// - `ParticipantUpdate` /// - `DataTrackSubscriberHandles` /// - /// Invoking for other response types not listed above will log an error. - /// /// Note: the local participant identity is required to exclude data tracks published by the /// local participant from being treated as remote tracks. /// - pub fn handle_signal_response(&self, res: &[u8], local_participant_identity: String) { - // TODO: consider returning a result - let Ok(Some(msg)) = proto::SignalResponse::decode(res).map(|res| res.message) else { - log::error!("Failed to decode signal response"); - return; - }; + pub fn handle_signal_response( + &self, + res: &[u8], + local_participant_identity: String, + ) -> Result<(), DataTrackSignalResponseError> { + let res = proto::SignalResponse::decode(res) + .map_err(|err| DataTrackSignalResponseError::Decode(err))?; + + let msg = res.message.ok_or(DataTrackSignalResponseError::EmptyMessage)?; + use proto::signal_response::Message; match msg { Message::Update(mut msg) => { - let Some(event) = - event_from_participant_update(&mut msg, &local_participant_identity) - .inspect_err(|err| log::error!("{err}")) - .ok() - else { - return; - }; + let event = event_from_participant_update(&mut msg, &local_participant_identity) + .map_err(|err| DataTrackSignalResponseError::Internal(err))?; _ = self.input.send(event.into()); } Message::DataTrackSubscriberHandles(msg) => { - let event: inner::SfuSubscriberHandles = msg.try_into().unwrap(); // TODO: handle error + let event: inner::SfuSubscriberHandles = + msg.try_into().map_err(|err| DataTrackSignalResponseError::Internal(err))?; _ = self.input.send(event.into()) } _ => { - log::error!("Unsupported signal response type"); + return Err(DataTrackSignalResponseError::UnsupportedType); } }; + Ok(()) } /// Handles a encoded packet received over the data channel. From 5ba3dc594ce4799938556363b9a4a601097d325e Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Tue, 21 Apr 2026 13:56:17 +0900 Subject: [PATCH 18/46] E2EE in separate mod, rename --- livekit-uniffi/src/data_track/e2ee.rs | 99 +++++++++++++++++++++++++ livekit-uniffi/src/data_track/local.rs | 55 ++------------ livekit-uniffi/src/data_track/mod.rs | 1 + livekit-uniffi/src/data_track/remote.rs | 48 ++---------- 4 files changed, 113 insertions(+), 90 deletions(-) create mode 100644 livekit-uniffi/src/data_track/e2ee.rs diff --git a/livekit-uniffi/src/data_track/e2ee.rs b/livekit-uniffi/src/data_track/e2ee.rs new file mode 100644 index 000000000..fecf4edb1 --- /dev/null +++ b/livekit-uniffi/src/data_track/e2ee.rs @@ -0,0 +1,99 @@ +// Copyright 2026 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use bytes::Bytes; +use core::fmt; +use livekit_datatrack::backend::{ + DecryptionError, DecryptionProvider, EncryptedPayload, EncryptionError, EncryptionProvider, + InitializationVector, +}; +use std::sync::Arc; + +#[uniffi::remote(Error)] +#[uniffi(flat_error)] +pub enum EncryptionError {} + +#[uniffi::remote(Error)] +#[uniffi(flat_error)] +pub enum DecryptionError {} + +#[uniffi::remote(Record)] +pub struct EncryptedPayload { + pub payload: Bytes, + pub iv: InitializationVector, + pub key_index: u8, +} + +uniffi::custom_type!(InitializationVector, Vec, { + remote, + lower: |iv| iv.to_vec(), + try_lift: |v| v.try_into() + .map_err(|_| uniffi::deps::anyhow::anyhow!("IV must be exactly 12 bytes")) +}); + +/// Provider for encrypting payloads for E2EE. +#[uniffi::export(with_foreign)] +pub trait DataTrackEncryptionProvider: Send + Sync { + /// Encrypts the given payload being sent by the local participant. + fn encrypt(&self, payload: Bytes) -> Result; +} + +/// Provider for decrypting payloads for E2EE. +#[uniffi::export(with_foreign)] +pub trait DataTrackDecryptionProvider: Send + Sync { + /// Decrypts the given payload received from a remote participant. + /// + /// Sender identity is required in order for the proper key to be used + /// for decryption. + /// + fn decrypt( + &self, + payload: EncryptedPayload, + sender_identity: String, + ) -> Result; +} + +/// Adapts [`DataTrackEncryptionProvider`] to implement [`EncryptionProvider`]. +pub(super) struct FfiEncryptionProvider(pub(super) Arc); + +impl EncryptionProvider for FfiEncryptionProvider { + fn encrypt(&self, payload: Bytes) -> Result { + self.0.encrypt(payload) + } +} + +impl fmt::Debug for FfiEncryptionProvider { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FfiEncryptionProvider").finish() + } +} + +/// Adapts [`DataTrackDecryptionProvider`] to implement [`DecryptionProvider`]. +pub(super) struct FfiDecryptionProvider(pub(super) Arc); + +impl DecryptionProvider for FfiDecryptionProvider { + fn decrypt( + &self, + payload: EncryptedPayload, + sender_identity: &str, + ) -> Result { + self.0.decrypt(payload, sender_identity.to_string()) + } +} + +impl fmt::Debug for FfiDecryptionProvider { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FfiDecryptionProvider").finish() + } +} diff --git a/livekit-uniffi/src/data_track/local.rs b/livekit-uniffi/src/data_track/local.rs index 362eab743..7e232ac7c 100644 --- a/livekit-uniffi/src/data_track/local.rs +++ b/livekit-uniffi/src/data_track/local.rs @@ -12,23 +12,23 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::data_track::DataTrackSignalResponseError; -use super::DataTrackInfo; +use super::{ + e2ee::{FfiEncryptionProvider, DataTrackEncryptionProvider}, + DataTrackInfo, DataTrackSignalResponseError, +}; use bytes::Bytes; use futures_util::{Stream, StreamExt}; use inner::OutputEvent; -use livekit_datatrack::api::{DataTrack, DataTrackFrame, Local, PushFrameErrorReason}; +use livekit_datatrack::{api::{DataTrack, DataTrackFrame, Local, PushFrameErrorReason}, backend::EncryptionProvider}; use livekit_datatrack::backend::local::{ publish_result_from_request_response, InputEvent, ManagerInput, SfuPublishResponse, }; -use livekit_datatrack::backend::EncryptionError; use livekit_datatrack::{ api::{DataTrackOptions, PublishError}, - backend::{local as inner, EncryptedPayload, EncryptionProvider, InitializationVector}, + backend::local as inner, }; use livekit_protocol as proto; use prost::Message; -use std::fmt; use std::sync::Arc; use tokio_util::sync::{CancellationToken, DropGuard}; @@ -101,7 +101,7 @@ impl LocalDataTrackManager { #[uniffi::constructor] pub fn new( delegate: Arc, - encryption_provider: Option>, + encryption_provider: Option>, ) -> Arc { let token = CancellationToken::new(); @@ -198,7 +198,6 @@ impl LocalDataTrackManager { /// - `PublishDataTrackResponse` /// pub fn handle_signal_response(&self, res: &[u8]) -> Result<(), DataTrackSignalResponseError> { - let res = proto::SignalResponse::decode(res) .map_err(|err| DataTrackSignalResponseError::Decode(err))?; @@ -237,43 +236,3 @@ pub trait LocalDataTrackManagerDelegate: Send + Sync { /// Packets available to be sent over the data channel transport. fn on_packets_available(&self, packets: Vec); } - -uniffi::custom_type!(InitializationVector, Vec, { - remote, - lower: |iv| iv.to_vec(), - try_lift: |v| v.try_into() - .map_err(|_| uniffi::deps::anyhow::anyhow!("IV must be exactly 12 bytes")) -}); - -#[uniffi::remote(Record)] -pub struct EncryptedPayload { - pub payload: Bytes, - pub iv: InitializationVector, - pub key_index: u8, -} - -#[uniffi::remote(Error)] -#[uniffi(flat_error)] -pub enum EncryptionError {} - -/// Provider for encrypting payloads for E2EE. -#[uniffi::export(with_foreign)] -pub trait LocalDataTrackEncryptionProvider: Send + Sync { - /// Encrypts the given payload being sent by the local participant. - fn encrypt(&self, payload: Bytes) -> Result; -} - -/// Adapts [`LocalDataTrackEncryptionProvider`] to implement [`EncryptionProvider`]. -struct FfiEncryptionProvider(Arc); - -impl EncryptionProvider for FfiEncryptionProvider { - fn encrypt(&self, payload: Bytes) -> Result { - self.0.encrypt(payload) - } -} - -impl fmt::Debug for FfiEncryptionProvider { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("FfiEncryptionProvider").finish_non_exhaustive() - } -} diff --git a/livekit-uniffi/src/data_track/mod.rs b/livekit-uniffi/src/data_track/mod.rs index e5007b12b..2befe8519 100644 --- a/livekit-uniffi/src/data_track/mod.rs +++ b/livekit-uniffi/src/data_track/mod.rs @@ -17,6 +17,7 @@ use livekit_datatrack::api::{DataTrackFrame, DataTrackSid}; pub mod local; pub mod remote; +pub mod e2ee; uniffi::custom_type!(DataTrackSid, String, { remote, diff --git a/livekit-uniffi/src/data_track/remote.rs b/livekit-uniffi/src/data_track/remote.rs index dbaaab4b8..4717cf2aa 100644 --- a/livekit-uniffi/src/data_track/remote.rs +++ b/livekit-uniffi/src/data_track/remote.rs @@ -12,15 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::data_track::{DataTrackInfo, DataTrackSignalResponseError}; +use super::{ + e2ee::{FfiDecryptionProvider, DataTrackDecryptionProvider}, + DataTrackInfo, DataTrackSignalResponseError, +}; use bytes::Bytes; -use core::fmt; use futures_util::{Stream, StreamExt}; use livekit_datatrack::{ api::{DataTrack, DataTrackFrame, DataTrackSid, DataTrackSubscribeError, Remote}, backend::{ remote::{self as inner, event_from_participant_update}, - DecryptionError, DecryptionProvider, EncryptedPayload, + DecryptionProvider, }, }; use livekit_protocol as proto; @@ -88,7 +90,7 @@ impl RemoteDataTrackManager { #[uniffi::constructor] pub fn new( delegate: Arc, - decryption_provider: Option>, + decryption_provider: Option>, ) -> Arc { let token = CancellationToken::new(); @@ -226,41 +228,3 @@ pub trait RemoteDataTrackManagerDelegate: Send + Sync { /// A track with the given SID has been unpublished by a remote participant. fn on_track_unpublished(&self, sid: DataTrackSid); } - -#[uniffi::remote(Error)] -#[uniffi(flat_error)] -pub enum DecryptionError {} - -/// Provider for decrypting payloads for E2EE. -#[uniffi::export(with_foreign)] -pub trait RemoteDataTrackDecryptionProvider: Send + Sync { - /// Decrypts the given payload received from a remote participant. - /// - /// Sender identity is required in order for the proper key to be used - /// for decryption. - /// - fn decrypt( - &self, - payload: EncryptedPayload, - sender_identity: String, - ) -> Result; -} - -/// Adapts [`RemoteDataTrackDecryptionProvider`] to implement [`DecryptionProvider`]. -struct FfiDecryptionProvider(Arc); - -impl DecryptionProvider for FfiDecryptionProvider { - fn decrypt( - &self, - payload: EncryptedPayload, - sender_identity: &str, - ) -> Result { - self.0.decrypt(payload, sender_identity.to_string()) - } -} - -impl fmt::Debug for FfiDecryptionProvider { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("FfiDecryptionProvider").finish_non_exhaustive() - } -} From 78f27cb2b7ec196445352ac8c482d2cfad389da7 Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Tue, 21 Apr 2026 15:23:28 +0900 Subject: [PATCH 19/46] Organization --- livekit-uniffi/src/data_track/local.rs | 107 ++++++++++---------- livekit-uniffi/src/data_track/remote.rs | 127 ++++++++++++------------ 2 files changed, 114 insertions(+), 120 deletions(-) diff --git a/livekit-uniffi/src/data_track/local.rs b/livekit-uniffi/src/data_track/local.rs index 7e232ac7c..343089fe8 100644 --- a/livekit-uniffi/src/data_track/local.rs +++ b/livekit-uniffi/src/data_track/local.rs @@ -96,6 +96,16 @@ struct LocalDataTrackManager { _drop_guard: DropGuard, } +/// Delegate for receiving output events from [`LocalDataTrackManager`]. +#[uniffi::export(with_foreign)] +pub trait LocalDataTrackManagerDelegate: Send + Sync { + /// Encoded signal request to be forwarded to the SFU. + fn on_signal_request(&self, request: Vec); + + /// Packets available to be sent over the data channel transport. + fn on_packets_available(&self, packets: Vec); +} + #[uniffi::export] impl LocalDataTrackManager { #[uniffi::constructor] @@ -116,54 +126,7 @@ impl LocalDataTrackManager { Self { input, _drop_guard: token.drop_guard() }.into() } -} - -impl LocalDataTrackManager { - async fn shutdown_forward_task(input: ManagerInput, token: CancellationToken) { - // TODO: consider having manager work with cancellation token out-of-the-box. - token.cancelled().await; - _ = input.send(InputEvent::Shutdown); - } - - async fn delegate_forward_task( - output: impl Stream, - delegate: Arc, - token: CancellationToken, - ) { - tokio::pin!(output); - loop { - tokio::select! { - _ = token.cancelled() => break, - Some(event) = output.next() => Self::forward_event(event, &delegate) - } - } - } - - fn forward_event(event: OutputEvent, delegate: &Arc) { - match event { - OutputEvent::PacketsAvailable(packets) => delegate.on_packets_available(packets), - OutputEvent::SfuPublishRequest(req) => { - let req = proto::signal_request::Message::PublishDataTrackRequest(req.into()); - Self::forward_signal_request(req, delegate); - } - OutputEvent::SfuUnpublishRequest(req) => { - let req = proto::signal_request::Message::UnpublishDataTrackRequest(req.into()); - Self::forward_signal_request(req, delegate); - } - } - } - - fn forward_signal_request( - message: proto::signal_request::Message, - delegate: &Arc, - ) { - let req = proto::SignalRequest { message: Some(message) }.encode_to_vec(); - delegate.on_signal_request(req); - } -} -#[uniffi::export] -impl LocalDataTrackManager { /// Publishes a data track with given options. pub async fn publish_track( &self, @@ -227,12 +190,46 @@ impl LocalDataTrackManager { } } -/// Delegate for receiving output events from [`LocalDataTrackManager`]. -#[uniffi::export(with_foreign)] -pub trait LocalDataTrackManagerDelegate: Send + Sync { - /// Encoded signal request to be forwarded to the SFU. - fn on_signal_request(&self, request: Vec); +impl LocalDataTrackManager { + async fn shutdown_forward_task(input: ManagerInput, token: CancellationToken) { + // TODO: consider having manager work with cancellation token out-of-the-box. + token.cancelled().await; + _ = input.send(InputEvent::Shutdown); + } - /// Packets available to be sent over the data channel transport. - fn on_packets_available(&self, packets: Vec); -} + async fn delegate_forward_task( + output: impl Stream, + delegate: Arc, + token: CancellationToken, + ) { + tokio::pin!(output); + loop { + tokio::select! { + _ = token.cancelled() => break, + Some(event) = output.next() => Self::forward_event(event, &delegate) + } + } + } + + fn forward_event(event: OutputEvent, delegate: &Arc) { + match event { + OutputEvent::PacketsAvailable(packets) => delegate.on_packets_available(packets), + OutputEvent::SfuPublishRequest(req) => { + let req = proto::signal_request::Message::PublishDataTrackRequest(req.into()); + Self::forward_signal_request(req, delegate); + } + OutputEvent::SfuUnpublishRequest(req) => { + let req = proto::signal_request::Message::UnpublishDataTrackRequest(req.into()); + Self::forward_signal_request(req, delegate); + } + } + } + + fn forward_signal_request( + message: proto::signal_request::Message, + delegate: &Arc, + ) { + let req = proto::SignalRequest { message: Some(message) }.encode_to_vec(); + delegate.on_signal_request(req); + } +} \ No newline at end of file diff --git a/livekit-uniffi/src/data_track/remote.rs b/livekit-uniffi/src/data_track/remote.rs index 4717cf2aa..aa36a36b0 100644 --- a/livekit-uniffi/src/data_track/remote.rs +++ b/livekit-uniffi/src/data_track/remote.rs @@ -85,6 +85,24 @@ struct RemoteDataTrackManager { _drop_guard: DropGuard, } +/// Delegate for receiving output events from [`RemoteDataTrackManager`]. +#[uniffi::export(with_foreign)] +pub trait RemoteDataTrackManagerDelegate: Send + Sync { + /// Encoded signal request to be forwarded to the SFU. + fn on_signal_request(&self, request: Vec); + + /// A track has been published by a remote participant and is available to be + /// subscribed to. + /// + /// Emit a public event to deliver the track to the user, allowing them to subscribe + /// with [`RemoteDataTrack::subscribe`] if desired. + /// + fn on_track_published(&self, track: Arc); + + /// A track with the given SID has been unpublished by a remote participant. + fn on_track_unpublished(&self, sid: DataTrackSid); +} + #[uniffi::export] impl RemoteDataTrackManager { #[uniffi::constructor] @@ -105,57 +123,7 @@ impl RemoteDataTrackManager { Self { input, _drop_guard: token.drop_guard() }.into() } -} - -impl RemoteDataTrackManager { - async fn shutdown_forward_task(input: inner::ManagerInput, token: CancellationToken) { - // TODO: consider having manager work with cancellation token out-of-the-box. - token.cancelled().await; - _ = input.send(inner::InputEvent::Shutdown); - } - - async fn delegate_forward_task( - output: impl Stream, - delegate: Arc, - token: CancellationToken, - ) { - tokio::pin!(output); - loop { - tokio::select! { - _ = token.cancelled() => break, - Some(event) = output.next() => Self::forward_event(event, &delegate) - } - } - } - - fn forward_event( - event: inner::OutputEvent, - delegate: &Arc, - ) { - match event { - inner::OutputEvent::SfuUpdateSubscription(req) => { - let req = proto::signal_request::Message::UpdateDataSubscription(req.into()); - Self::forward_signal_request(req, delegate); - } - inner::OutputEvent::TrackPublished(event) => { - let track = Arc::new(RemoteDataTrack(event.track)); - delegate.on_track_published(track); - } - inner::OutputEvent::TrackUnpublished(event) => delegate.on_track_unpublished(event.sid), - } - } - - fn forward_signal_request( - message: proto::signal_request::Message, - delegate: &Arc, - ) { - let req = proto::SignalRequest { message: Some(message) }.encode_to_vec(); - delegate.on_signal_request(req); - } -} -#[uniffi::export] -impl RemoteDataTrackManager { /// Resend all subscription updates. /// /// This must be sent after a full reconnect to ensure the SFU knows which tracks @@ -211,20 +179,49 @@ impl RemoteDataTrackManager { } } -/// Delegate for receiving output events from [`RemoteDataTrackManager`]. -#[uniffi::export(with_foreign)] -pub trait RemoteDataTrackManagerDelegate: Send + Sync { - /// Encoded signal request to be forwarded to the SFU. - fn on_signal_request(&self, request: Vec); +impl RemoteDataTrackManager { + async fn shutdown_forward_task(input: inner::ManagerInput, token: CancellationToken) { + // TODO: consider having manager work with cancellation token out-of-the-box. + token.cancelled().await; + _ = input.send(inner::InputEvent::Shutdown); + } - /// A track has been published by a remote participant and is available to be - /// subscribed to. - /// - /// Emit a public event to deliver the track to the user, allowing them to subscribe - /// with [`RemoteDataTrack::subscribe`] if desired. - /// - fn on_track_published(&self, track: Arc); + async fn delegate_forward_task( + output: impl Stream, + delegate: Arc, + token: CancellationToken, + ) { + tokio::pin!(output); + loop { + tokio::select! { + _ = token.cancelled() => break, + Some(event) = output.next() => Self::forward_event(event, &delegate) + } + } + } - /// A track with the given SID has been unpublished by a remote participant. - fn on_track_unpublished(&self, sid: DataTrackSid); -} + fn forward_event( + event: inner::OutputEvent, + delegate: &Arc, + ) { + match event { + inner::OutputEvent::SfuUpdateSubscription(req) => { + let req = proto::signal_request::Message::UpdateDataSubscription(req.into()); + Self::forward_signal_request(req, delegate); + } + inner::OutputEvent::TrackPublished(event) => { + let track = Arc::new(RemoteDataTrack(event.track)); + delegate.on_track_published(track); + } + inner::OutputEvent::TrackUnpublished(event) => delegate.on_track_unpublished(event.sid), + } + } + + fn forward_signal_request( + message: proto::signal_request::Message, + delegate: &Arc, + ) { + let req = proto::SignalRequest { message: Some(message) }.encode_to_vec(); + delegate.on_signal_request(req); + } +} \ No newline at end of file From 26b61c71df4faa71fb76bce591fe38da044b05f0 Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Tue, 21 Apr 2026 15:24:40 +0900 Subject: [PATCH 20/46] Rename --- livekit-uniffi/src/data_track/local.rs | 10 +++++----- livekit-uniffi/src/data_track/mod.rs | 4 ++-- livekit-uniffi/src/data_track/remote.rs | 14 +++++++------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/livekit-uniffi/src/data_track/local.rs b/livekit-uniffi/src/data_track/local.rs index 343089fe8..5929e326e 100644 --- a/livekit-uniffi/src/data_track/local.rs +++ b/livekit-uniffi/src/data_track/local.rs @@ -14,7 +14,7 @@ use super::{ e2ee::{FfiEncryptionProvider, DataTrackEncryptionProvider}, - DataTrackInfo, DataTrackSignalResponseError, + DataTrackInfo, HandleSignalResponseError, }; use bytes::Bytes; use futures_util::{Stream, StreamExt}; @@ -160,11 +160,11 @@ impl LocalDataTrackManager { /// - `RequestResponse` /// - `PublishDataTrackResponse` /// - pub fn handle_signal_response(&self, res: &[u8]) -> Result<(), DataTrackSignalResponseError> { + pub fn handle_signal_response(&self, res: &[u8]) -> Result<(), HandleSignalResponseError> { let res = proto::SignalResponse::decode(res) - .map_err(|err| DataTrackSignalResponseError::Decode(err))?; + .map_err(|err| HandleSignalResponseError::Decode(err))?; - let msg = res.message.ok_or(DataTrackSignalResponseError::EmptyMessage)?; + let msg = res.message.ok_or(HandleSignalResponseError::EmptyMessage)?; use proto::signal_response::Message; let publish_res = match msg { @@ -180,7 +180,7 @@ impl LocalDataTrackManager { let res: SfuPublishResponse = res.try_into().unwrap(); res } - _ => return Err(DataTrackSignalResponseError::UnsupportedType), + _ => return Err(HandleSignalResponseError::UnsupportedType), }; let event: InputEvent = publish_res.into(); diff --git a/livekit-uniffi/src/data_track/mod.rs b/livekit-uniffi/src/data_track/mod.rs index 2befe8519..f69a4fd2a 100644 --- a/livekit-uniffi/src/data_track/mod.rs +++ b/livekit-uniffi/src/data_track/mod.rs @@ -44,10 +44,10 @@ impl From<&livekit_datatrack::api::DataTrackInfo> for DataTrackInfo { } } -/// Signal response could not be handled. +/// Signal response crossing the FFI boundary could not be processed. #[derive(uniffi::Error, thiserror::Error, Debug)] #[uniffi(flat_error)] -pub enum DataTrackSignalResponseError { +pub enum HandleSignalResponseError { #[error("Response decoding failed: {0}")] Decode(prost::DecodeError), #[error("Response container has no message")] diff --git a/livekit-uniffi/src/data_track/remote.rs b/livekit-uniffi/src/data_track/remote.rs index aa36a36b0..85b702e54 100644 --- a/livekit-uniffi/src/data_track/remote.rs +++ b/livekit-uniffi/src/data_track/remote.rs @@ -14,7 +14,7 @@ use super::{ e2ee::{FfiDecryptionProvider, DataTrackDecryptionProvider}, - DataTrackInfo, DataTrackSignalResponseError, + DataTrackInfo, HandleSignalResponseError, }; use bytes::Bytes; use futures_util::{Stream, StreamExt}; @@ -148,26 +148,26 @@ impl RemoteDataTrackManager { &self, res: &[u8], local_participant_identity: String, - ) -> Result<(), DataTrackSignalResponseError> { + ) -> Result<(), HandleSignalResponseError> { let res = proto::SignalResponse::decode(res) - .map_err(|err| DataTrackSignalResponseError::Decode(err))?; + .map_err(|err| HandleSignalResponseError::Decode(err))?; - let msg = res.message.ok_or(DataTrackSignalResponseError::EmptyMessage)?; + let msg = res.message.ok_or(HandleSignalResponseError::EmptyMessage)?; use proto::signal_response::Message; match msg { Message::Update(mut msg) => { let event = event_from_participant_update(&mut msg, &local_participant_identity) - .map_err(|err| DataTrackSignalResponseError::Internal(err))?; + .map_err(|err| HandleSignalResponseError::Internal(err))?; _ = self.input.send(event.into()); } Message::DataTrackSubscriberHandles(msg) => { let event: inner::SfuSubscriberHandles = - msg.try_into().map_err(|err| DataTrackSignalResponseError::Internal(err))?; + msg.try_into().map_err(|err| HandleSignalResponseError::Internal(err))?; _ = self.input.send(event.into()) } _ => { - return Err(DataTrackSignalResponseError::UnsupportedType); + return Err(HandleSignalResponseError::UnsupportedType); } }; Ok(()) From e1aba7d63282f6cd842b7f4ddc8abe3cffe7c6bf Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Tue, 21 Apr 2026 15:24:48 +0900 Subject: [PATCH 21/46] Add thiserror as dependency --- livekit-uniffi/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/livekit-uniffi/Cargo.toml b/livekit-uniffi/Cargo.toml index 6b17385e0..383d6b186 100644 --- a/livekit-uniffi/Cargo.toml +++ b/livekit-uniffi/Cargo.toml @@ -23,6 +23,7 @@ prost = "0.12" futures-util = { workspace = true, default-features = false, features = ["sink"] } bytes = { workspace = true } once_cell = "1.21.3" +thiserror = { workspace = true } [build-dependencies] uniffi = { version = "0.30.0", features = ["build", "scaffolding-ffi-buffer-fns"] } From ca653fb9d1634872677601a609a46ec58b18d3cb Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Tue, 21 Apr 2026 15:41:12 +0900 Subject: [PATCH 22/46] Doc --- livekit-uniffi/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/livekit-uniffi/src/lib.rs b/livekit-uniffi/src/lib.rs index 4ac01bf09..d2030dab1 100644 --- a/livekit-uniffi/src/lib.rs +++ b/livekit-uniffi/src/lib.rs @@ -24,6 +24,7 @@ pub mod log_forward; /// Information about the build such as version. pub mod build_info; +/// Shared exports and utilities. pub mod common; uniffi::setup_scaffolding!(); From cc742dafbdca780dbbdfe23dba49b8ef31419937 Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Tue, 21 Apr 2026 15:42:47 +0900 Subject: [PATCH 23/46] Handle error --- livekit-uniffi/src/data_track/local.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/livekit-uniffi/src/data_track/local.rs b/livekit-uniffi/src/data_track/local.rs index 5929e326e..1faaa6179 100644 --- a/livekit-uniffi/src/data_track/local.rs +++ b/livekit-uniffi/src/data_track/local.rs @@ -13,16 +13,19 @@ // limitations under the License. use super::{ - e2ee::{FfiEncryptionProvider, DataTrackEncryptionProvider}, + e2ee::{DataTrackEncryptionProvider, FfiEncryptionProvider}, DataTrackInfo, HandleSignalResponseError, }; use bytes::Bytes; use futures_util::{Stream, StreamExt}; use inner::OutputEvent; -use livekit_datatrack::{api::{DataTrack, DataTrackFrame, Local, PushFrameErrorReason}, backend::EncryptionProvider}; use livekit_datatrack::backend::local::{ publish_result_from_request_response, InputEvent, ManagerInput, SfuPublishResponse, }; +use livekit_datatrack::{ + api::{DataTrack, DataTrackFrame, Local, PushFrameErrorReason}, + backend::EncryptionProvider, +}; use livekit_datatrack::{ api::{DataTrackOptions, PublishError}, backend::local as inner, @@ -176,8 +179,8 @@ impl LocalDataTrackManager { res } Message::PublishDataTrackResponse(res) => { - // TODO: handle error - let res: SfuPublishResponse = res.try_into().unwrap(); + let res: SfuPublishResponse = + res.try_into().map_err(|err| HandleSignalResponseError::Internal(err))?; res } _ => return Err(HandleSignalResponseError::UnsupportedType), @@ -232,4 +235,4 @@ impl LocalDataTrackManager { let req = proto::SignalRequest { message: Some(message) }.encode_to_vec(); delegate.on_signal_request(req); } -} \ No newline at end of file +} From 0911f3f1887a3092bedf0aae97e3541e9a588ca1 Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Tue, 21 Apr 2026 15:49:09 +0900 Subject: [PATCH 24/46] Consistent namespacing --- livekit-uniffi/src/data_track/local.rs | 40 ++++++++++------------- livekit-uniffi/src/data_track/remote.rs | 42 ++++++++++++------------- 2 files changed, 37 insertions(+), 45 deletions(-) diff --git a/livekit-uniffi/src/data_track/local.rs b/livekit-uniffi/src/data_track/local.rs index 1faaa6179..d7ccb3b19 100644 --- a/livekit-uniffi/src/data_track/local.rs +++ b/livekit-uniffi/src/data_track/local.rs @@ -18,17 +18,9 @@ use super::{ }; use bytes::Bytes; use futures_util::{Stream, StreamExt}; -use inner::OutputEvent; -use livekit_datatrack::backend::local::{ - publish_result_from_request_response, InputEvent, ManagerInput, SfuPublishResponse, -}; -use livekit_datatrack::{ - api::{DataTrack, DataTrackFrame, Local, PushFrameErrorReason}, - backend::EncryptionProvider, -}; use livekit_datatrack::{ - api::{DataTrackOptions, PublishError}, - backend::local as inner, + api::{DataTrack, DataTrackFrame, DataTrackOptions, Local, PublishError, PushFrameErrorReason}, + backend::{local, EncryptionProvider}, }; use livekit_protocol as proto; use prost::Message; @@ -95,7 +87,7 @@ impl LocalDataTrack { /// #[derive(uniffi::Object)] struct LocalDataTrackManager { - input: inner::ManagerInput, + input: local::ManagerInput, _drop_guard: DropGuard, } @@ -120,9 +112,9 @@ impl LocalDataTrackManager { let encryption_provider = encryption_provider .map(|p| Arc::new(FfiEncryptionProvider(p)) as Arc); - let manager_options = inner::ManagerOptions { encryption_provider }; + let manager_options = local::ManagerOptions { encryption_provider }; - let (manager, input, output) = inner::Manager::new(manager_options); + let (manager, input, output) = local::Manager::new(manager_options); tokio::spawn(Self::shutdown_forward_task(input.clone(), token.clone())); tokio::spawn(Self::delegate_forward_task(output, delegate, token.clone())); tokio::spawn(manager.run()); @@ -152,7 +144,7 @@ impl LocalDataTrackManager { /// to be recognized by the SFU. Each republished track will be assigned a new SID. /// pub async fn republish_tracks(&self) { - _ = self.input.send(inner::InputEvent::RepublishTracks); + _ = self.input.send(local::InputEvent::RepublishTracks); } /// Handles a serialized signal response from the SFU. @@ -172,21 +164,21 @@ impl LocalDataTrackManager { use proto::signal_response::Message; let publish_res = match msg { Message::RequestResponse(msg) => { - let Some(res) = publish_result_from_request_response(&msg) else { + let Some(res) = local::publish_result_from_request_response(&msg) else { // Not from data track publish request. return Ok(()); }; res } Message::PublishDataTrackResponse(res) => { - let res: SfuPublishResponse = + let res: local::SfuPublishResponse = res.try_into().map_err(|err| HandleSignalResponseError::Internal(err))?; res } _ => return Err(HandleSignalResponseError::UnsupportedType), }; - let event: InputEvent = publish_res.into(); + let event: local::InputEvent = publish_res.into(); _ = self.input.send(event); Ok(()) @@ -194,14 +186,14 @@ impl LocalDataTrackManager { } impl LocalDataTrackManager { - async fn shutdown_forward_task(input: ManagerInput, token: CancellationToken) { + async fn shutdown_forward_task(input: local::ManagerInput, token: CancellationToken) { // TODO: consider having manager work with cancellation token out-of-the-box. token.cancelled().await; - _ = input.send(InputEvent::Shutdown); + _ = input.send(local::InputEvent::Shutdown); } async fn delegate_forward_task( - output: impl Stream, + output: impl Stream, delegate: Arc, token: CancellationToken, ) { @@ -214,14 +206,14 @@ impl LocalDataTrackManager { } } - fn forward_event(event: OutputEvent, delegate: &Arc) { + fn forward_event(event: local::OutputEvent, delegate: &Arc) { match event { - OutputEvent::PacketsAvailable(packets) => delegate.on_packets_available(packets), - OutputEvent::SfuPublishRequest(req) => { + local::OutputEvent::PacketsAvailable(packets) => delegate.on_packets_available(packets), + local::OutputEvent::SfuPublishRequest(req) => { let req = proto::signal_request::Message::PublishDataTrackRequest(req.into()); Self::forward_signal_request(req, delegate); } - OutputEvent::SfuUnpublishRequest(req) => { + local::OutputEvent::SfuUnpublishRequest(req) => { let req = proto::signal_request::Message::UnpublishDataTrackRequest(req.into()); Self::forward_signal_request(req, delegate); } diff --git a/livekit-uniffi/src/data_track/remote.rs b/livekit-uniffi/src/data_track/remote.rs index 85b702e54..77f54c499 100644 --- a/livekit-uniffi/src/data_track/remote.rs +++ b/livekit-uniffi/src/data_track/remote.rs @@ -13,17 +13,14 @@ // limitations under the License. use super::{ - e2ee::{FfiDecryptionProvider, DataTrackDecryptionProvider}, + e2ee::{DataTrackDecryptionProvider, FfiDecryptionProvider}, DataTrackInfo, HandleSignalResponseError, }; use bytes::Bytes; use futures_util::{Stream, StreamExt}; use livekit_datatrack::{ api::{DataTrack, DataTrackFrame, DataTrackSid, DataTrackSubscribeError, Remote}, - backend::{ - remote::{self as inner, event_from_participant_update}, - DecryptionProvider, - }, + backend::{remote, DecryptionProvider}, }; use livekit_protocol as proto; use prost::Message; @@ -81,7 +78,7 @@ impl DataTrackStream { /// System for managing data track subscriptions. #[derive(uniffi::Object)] struct RemoteDataTrackManager { - input: inner::ManagerInput, + input: remote::ManagerInput, _drop_guard: DropGuard, } @@ -114,9 +111,9 @@ impl RemoteDataTrackManager { let decryption_provider = decryption_provider .map(|p| Arc::new(FfiDecryptionProvider(p)) as Arc); - let manager_options = inner::ManagerOptions { decryption_provider }; + let manager_options = remote::ManagerOptions { decryption_provider }; - let (manager, input, output) = inner::Manager::new(manager_options); + let (manager, input, output) = remote::Manager::new(manager_options); tokio::spawn(Self::shutdown_forward_task(input.clone(), token.clone())); tokio::spawn(Self::delegate_forward_task(output, delegate, token.clone())); tokio::spawn(manager.run()); @@ -130,7 +127,7 @@ impl RemoteDataTrackManager { /// are subscribed to locally. /// pub fn resend_subscription_updates(&self) { - _ = self.input.send(inner::InputEvent::ResendSubscriptionUpdates); + _ = self.input.send(remote::InputEvent::ResendSubscriptionUpdates); } /// Handles a serialized signal response from the SFU. @@ -157,12 +154,13 @@ impl RemoteDataTrackManager { use proto::signal_response::Message; match msg { Message::Update(mut msg) => { - let event = event_from_participant_update(&mut msg, &local_participant_identity) - .map_err(|err| HandleSignalResponseError::Internal(err))?; + let event = + remote::event_from_participant_update(&mut msg, &local_participant_identity) + .map_err(|err| HandleSignalResponseError::Internal(err))?; _ = self.input.send(event.into()); } Message::DataTrackSubscriberHandles(msg) => { - let event: inner::SfuSubscriberHandles = + let event: remote::SfuSubscriberHandles = msg.try_into().map_err(|err| HandleSignalResponseError::Internal(err))?; _ = self.input.send(event.into()) } @@ -175,19 +173,19 @@ impl RemoteDataTrackManager { /// Handles a encoded packet received over the data channel. pub fn handle_packet_received(&self, packet: Bytes) { - _ = self.input.send(inner::InputEvent::PacketReceived(packet)) + _ = self.input.send(remote::InputEvent::PacketReceived(packet)) } } impl RemoteDataTrackManager { - async fn shutdown_forward_task(input: inner::ManagerInput, token: CancellationToken) { + async fn shutdown_forward_task(input: remote::ManagerInput, token: CancellationToken) { // TODO: consider having manager work with cancellation token out-of-the-box. token.cancelled().await; - _ = input.send(inner::InputEvent::Shutdown); + _ = input.send(remote::InputEvent::Shutdown); } async fn delegate_forward_task( - output: impl Stream, + output: impl Stream, delegate: Arc, token: CancellationToken, ) { @@ -201,19 +199,21 @@ impl RemoteDataTrackManager { } fn forward_event( - event: inner::OutputEvent, + event: remote::OutputEvent, delegate: &Arc, ) { match event { - inner::OutputEvent::SfuUpdateSubscription(req) => { + remote::OutputEvent::SfuUpdateSubscription(req) => { let req = proto::signal_request::Message::UpdateDataSubscription(req.into()); Self::forward_signal_request(req, delegate); } - inner::OutputEvent::TrackPublished(event) => { + remote::OutputEvent::TrackPublished(event) => { let track = Arc::new(RemoteDataTrack(event.track)); delegate.on_track_published(track); } - inner::OutputEvent::TrackUnpublished(event) => delegate.on_track_unpublished(event.sid), + remote::OutputEvent::TrackUnpublished(event) => { + delegate.on_track_unpublished(event.sid) + } } } @@ -224,4 +224,4 @@ impl RemoteDataTrackManager { let req = proto::SignalRequest { message: Some(message) }.encode_to_vec(); delegate.on_signal_request(req); } -} \ No newline at end of file +} From 25446d3caba2f2c5a41a94b324723a7da4d06177 Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Tue, 21 Apr 2026 15:53:11 +0900 Subject: [PATCH 25/46] Organization, style --- livekit-uniffi/src/data_track/local.rs | 50 ++++++++++++------------- livekit-uniffi/src/data_track/remote.rs | 23 ++++++------ 2 files changed, 37 insertions(+), 36 deletions(-) diff --git a/livekit-uniffi/src/data_track/local.rs b/livekit-uniffi/src/data_track/local.rs index d7ccb3b19..13165948c 100644 --- a/livekit-uniffi/src/data_track/local.rs +++ b/livekit-uniffi/src/data_track/local.rs @@ -27,29 +27,6 @@ use prost::Message; use std::sync::Arc; use tokio_util::sync::{CancellationToken, DropGuard}; -#[uniffi::remote(Record)] -pub struct DataTrackOptions { - pub name: String, -} - -#[uniffi::remote(Error)] -pub enum PushFrameErrorReason { - TrackUnpublished, - QueueFull, -} - -#[uniffi::remote(Error)] -#[uniffi(flat_error)] -pub enum PublishError { - NotAllowed, - DuplicateName, - InvalidName, - Timeout, - LimitReached, - Disconnected, - Internal, -} - /// Data track published by the local participant. #[derive(uniffi::Object)] pub struct LocalDataTrack(DataTrack); @@ -79,6 +56,29 @@ impl LocalDataTrack { } } +#[uniffi::remote(Error)] +pub enum PushFrameErrorReason { + TrackUnpublished, + QueueFull, +} + +#[uniffi::remote(Record)] +pub struct DataTrackOptions { + pub name: String, +} + +#[uniffi::remote(Error)] +#[uniffi(flat_error)] +pub enum PublishError { + NotAllowed, + DuplicateName, + InvalidName, + Timeout, + LimitReached, + Disconnected, + Internal, +} + /// System for managing data track publications. /// /// FFI clients construct an instance of this inside `Room`, @@ -88,7 +88,7 @@ impl LocalDataTrack { #[derive(uniffi::Object)] struct LocalDataTrackManager { input: local::ManagerInput, - _drop_guard: DropGuard, + _guard: DropGuard, } /// Delegate for receiving output events from [`LocalDataTrackManager`]. @@ -119,7 +119,7 @@ impl LocalDataTrackManager { tokio::spawn(Self::delegate_forward_task(output, delegate, token.clone())); tokio::spawn(manager.run()); - Self { input, _drop_guard: token.drop_guard() }.into() + Self { input, _guard: token.drop_guard() }.into() } /// Publishes a data track with given options. diff --git a/livekit-uniffi/src/data_track/remote.rs b/livekit-uniffi/src/data_track/remote.rs index 77f54c499..c7f45229a 100644 --- a/livekit-uniffi/src/data_track/remote.rs +++ b/livekit-uniffi/src/data_track/remote.rs @@ -28,15 +28,6 @@ use std::sync::Arc; use tokio::sync::Mutex; use tokio_util::sync::{CancellationToken, DropGuard}; -#[uniffi::remote(Error)] -#[uniffi(flat_error)] -pub enum DataTrackSubscribeError { - Unpublished, - Timeout, - Disconnected, - Internal, -} - /// Data track published by the local participant. #[derive(uniffi::Object)] pub struct RemoteDataTrack(DataTrack); @@ -64,6 +55,16 @@ impl RemoteDataTrack { } } +#[uniffi::remote(Error)] +#[uniffi(flat_error)] +pub enum DataTrackSubscribeError { + Unpublished, + Timeout, + Disconnected, + Internal, +} + +/// A stream of [`DataTrackFrame`]s received from a [`RemoteDataTrack`]. #[derive(uniffi::Object)] struct DataTrackStream(Mutex); @@ -79,7 +80,7 @@ impl DataTrackStream { #[derive(uniffi::Object)] struct RemoteDataTrackManager { input: remote::ManagerInput, - _drop_guard: DropGuard, + _guard: DropGuard, } /// Delegate for receiving output events from [`RemoteDataTrackManager`]. @@ -118,7 +119,7 @@ impl RemoteDataTrackManager { tokio::spawn(Self::delegate_forward_task(output, delegate, token.clone())); tokio::spawn(manager.run()); - Self { input, _drop_guard: token.drop_guard() }.into() + Self { input, _guard: token.drop_guard() }.into() } /// Resend all subscription updates. From 807305af1fcaa376559653d15e7021375816ff7d Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Tue, 21 Apr 2026 16:03:12 +0900 Subject: [PATCH 26/46] Make manager output concrete type --- livekit-datatrack/src/local/manager.rs | 27 ++++++++++++++++++++++--- livekit-datatrack/src/remote/manager.rs | 21 +++++++++++++++++-- livekit-uniffi/src/data_track/local.rs | 5 ++--- livekit-uniffi/src/data_track/remote.rs | 5 ++--- livekit/src/room/mod.rs | 6 +++--- 5 files changed, 50 insertions(+), 14 deletions(-) diff --git a/livekit-datatrack/src/local/manager.rs b/livekit-datatrack/src/local/manager.rs index 15d76c318..0d99d1826 100644 --- a/livekit-datatrack/src/local/manager.rs +++ b/livekit-datatrack/src/local/manager.rs @@ -25,7 +25,13 @@ use crate::{ }; use anyhow::{anyhow, Context}; use futures_core::Stream; -use std::{collections::HashMap, sync::Arc, time::Duration}; +use std::{ + collections::HashMap, + pin::Pin, + sync::Arc, + task::{Context as TaskContext, Poll}, + time::Duration, +}; use tokio::sync::{mpsc, oneshot, watch}; use tokio_stream::wrappers::ReceiverStream; @@ -58,7 +64,7 @@ impl Manager { /// - Channel for sending [`InputEvent`]s to be processed by the manager. /// - Stream for receiving [`OutputEvent`]s produced by the manager. /// - pub fn new(options: ManagerOptions) -> (Self, ManagerInput, impl Stream) { + pub fn new(options: ManagerOptions) -> (Self, ManagerInput, ManagerOutput) { let (event_in_tx, event_in_rx) = mpsc::channel(Self::EVENT_BUFFER_COUNT); let (event_out_tx, event_out_rx) = mpsc::channel(Self::EVENT_BUFFER_COUNT); @@ -72,7 +78,7 @@ impl Manager { descriptors: HashMap::new(), }; - let event_out = ReceiverStream::new(event_out_rx); + let event_out = ManagerOutput(ReceiverStream::new(event_out_rx)); (manager, event_in, event_out) } @@ -400,6 +406,21 @@ pub struct ManagerInput { _drop_guard: Arc, } +/// Stream of [`OutputEvent`]s produced by [`Manager`]. +#[derive(Debug)] +pub struct ManagerOutput(ReceiverStream); + +impl Stream for ManagerOutput { + type Item = OutputEvent; + + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + ) -> Poll> { + Pin::new(&mut self.0).poll_next(cx) + } +} + /// Guard that sends shutdown event when the last reference is dropped. #[derive(Debug)] struct DropGuard { diff --git a/livekit-datatrack/src/remote/manager.rs b/livekit-datatrack/src/remote/manager.rs index 14447ceb5..e7e008083 100644 --- a/livekit-datatrack/src/remote/manager.rs +++ b/livekit-datatrack/src/remote/manager.rs @@ -27,7 +27,9 @@ use bytes::Bytes; use std::{ collections::{HashMap, HashSet}, mem, + pin::Pin, sync::Arc, + task::{Context as TaskContext, Poll}, }; use tokio::sync::{broadcast, mpsc, oneshot, watch}; use tokio_stream::{wrappers::ReceiverStream, Stream}; @@ -70,7 +72,7 @@ impl Manager { /// - Channel for sending [`InputEvent`]s to be processed by the manager. /// - Stream for receiving [`OutputEvent`]s produced by the manager. /// - pub fn new(options: ManagerOptions) -> (Self, ManagerInput, impl Stream) { + pub fn new(options: ManagerOptions) -> (Self, ManagerInput, ManagerOutput) { let (event_in_tx, event_in_rx) = mpsc::channel(Self::EVENT_BUFFER_COUNT); let (event_out_tx, event_out_rx) = mpsc::channel(Self::EVENT_BUFFER_COUNT); @@ -84,7 +86,7 @@ impl Manager { sub_handles: HashMap::default(), }; - let event_out = ReceiverStream::new(event_out_rx); + let event_out = ManagerOutput(ReceiverStream::new(event_out_rx)); (manager, event_in, event_out) } @@ -443,6 +445,21 @@ pub struct ManagerInput { _drop_guard: Arc, } +/// Stream of [`OutputEvent`]s produced by [`Manager`]. +#[derive(Debug)] +pub struct ManagerOutput(ReceiverStream); + +impl Stream for ManagerOutput { + type Item = OutputEvent; + + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + ) -> Poll> { + Pin::new(&mut self.0).poll_next(cx) + } +} + /// Guard that sends shutdown event when the last reference is dropped. #[derive(Debug)] struct DropGuard { diff --git a/livekit-uniffi/src/data_track/local.rs b/livekit-uniffi/src/data_track/local.rs index 13165948c..390c134c0 100644 --- a/livekit-uniffi/src/data_track/local.rs +++ b/livekit-uniffi/src/data_track/local.rs @@ -17,7 +17,7 @@ use super::{ DataTrackInfo, HandleSignalResponseError, }; use bytes::Bytes; -use futures_util::{Stream, StreamExt}; +use futures_util::StreamExt; use livekit_datatrack::{ api::{DataTrack, DataTrackFrame, DataTrackOptions, Local, PublishError, PushFrameErrorReason}, backend::{local, EncryptionProvider}, @@ -193,11 +193,10 @@ impl LocalDataTrackManager { } async fn delegate_forward_task( - output: impl Stream, + mut output: local::ManagerOutput, delegate: Arc, token: CancellationToken, ) { - tokio::pin!(output); loop { tokio::select! { _ = token.cancelled() => break, diff --git a/livekit-uniffi/src/data_track/remote.rs b/livekit-uniffi/src/data_track/remote.rs index c7f45229a..d6d51fde4 100644 --- a/livekit-uniffi/src/data_track/remote.rs +++ b/livekit-uniffi/src/data_track/remote.rs @@ -17,7 +17,7 @@ use super::{ DataTrackInfo, HandleSignalResponseError, }; use bytes::Bytes; -use futures_util::{Stream, StreamExt}; +use futures_util::StreamExt; use livekit_datatrack::{ api::{DataTrack, DataTrackFrame, DataTrackSid, DataTrackSubscribeError, Remote}, backend::{remote, DecryptionProvider}, @@ -186,11 +186,10 @@ impl RemoteDataTrackManager { } async fn delegate_forward_task( - output: impl Stream, + mut output: remote::ManagerOutput, delegate: Arc, token: CancellationToken, ) { - tokio::pin!(output); loop { tokio::select! { _ = token.cancelled() => break, diff --git a/livekit/src/room/mod.rs b/livekit/src/room/mod.rs index 0035237b6..e53b9e801 100644 --- a/livekit/src/room/mod.rs +++ b/livekit/src/room/mod.rs @@ -13,7 +13,7 @@ // limitations under the License. use bmrng::unbounded::UnboundedRequestReceiver; -use futures_util::{Stream, StreamExt}; +use futures_util::StreamExt; use libwebrtc::{ native::frame_cryptor::EncryptionState, prelude::{ @@ -2006,7 +2006,7 @@ impl RoomSession { /// Task for handling output events from the local data track manager. async fn local_dt_forward_task( self: Arc, - mut events: impl Stream + Unpin, + mut events: dt::local::ManagerOutput, mut close_rx: broadcast::Receiver<()>, ) { loop { @@ -2026,7 +2026,7 @@ impl RoomSession { /// Task for handling output events from the remote data track manager. async fn remote_dt_forward_task( self: Arc, - mut events: impl Stream + Unpin, + mut events: dt::remote::ManagerOutput, mut close_rx: broadcast::Receiver<()>, ) { loop { From c06705ca06e6b2f6852c95d9f24fa2a4a44249a5 Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Tue, 21 Apr 2026 16:23:08 +0900 Subject: [PATCH 27/46] Use actor pattern --- livekit-uniffi/src/data_track/local.rs | 53 ++++++++++++---------- livekit-uniffi/src/data_track/remote.rs | 60 ++++++++++++------------- 2 files changed, 59 insertions(+), 54 deletions(-) diff --git a/livekit-uniffi/src/data_track/local.rs b/livekit-uniffi/src/data_track/local.rs index 390c134c0..3d7916554 100644 --- a/livekit-uniffi/src/data_track/local.rs +++ b/livekit-uniffi/src/data_track/local.rs @@ -115,8 +115,11 @@ impl LocalDataTrackManager { let manager_options = local::ManagerOptions { encryption_provider }; let (manager, input, output) = local::Manager::new(manager_options); - tokio::spawn(Self::shutdown_forward_task(input.clone(), token.clone())); - tokio::spawn(Self::delegate_forward_task(output, delegate, token.clone())); + tokio::spawn(shutdown_forward_task(input.clone(), token.clone())); + + let delegate_forward = DelegateForwardTask { output, delegate, token: token.clone() }; + tokio::spawn(delegate_forward.run()); + tokio::spawn(manager.run()); Self { input, _guard: token.drop_guard() }.into() @@ -185,45 +188,47 @@ impl LocalDataTrackManager { } } -impl LocalDataTrackManager { - async fn shutdown_forward_task(input: local::ManagerInput, token: CancellationToken) { - // TODO: consider having manager work with cancellation token out-of-the-box. - token.cancelled().await; - _ = input.send(local::InputEvent::Shutdown); - } +/// Task for forwarding manger output events to the foreign [`LocalDataTrackManagerDelegate`]. +struct DelegateForwardTask { + output: local::ManagerOutput, + delegate: Arc, + token: CancellationToken, +} - async fn delegate_forward_task( - mut output: local::ManagerOutput, - delegate: Arc, - token: CancellationToken, - ) { +impl DelegateForwardTask { + async fn run(mut self) { loop { tokio::select! { - _ = token.cancelled() => break, - Some(event) = output.next() => Self::forward_event(event, &delegate) + _ = self.token.cancelled() => break, + Some(event) = self.output.next() => self.forward_event(event) } } } - fn forward_event(event: local::OutputEvent, delegate: &Arc) { + fn forward_event(&self, event: local::OutputEvent) { match event { - local::OutputEvent::PacketsAvailable(packets) => delegate.on_packets_available(packets), + local::OutputEvent::PacketsAvailable(packets) => { + self.delegate.on_packets_available(packets) + } local::OutputEvent::SfuPublishRequest(req) => { let req = proto::signal_request::Message::PublishDataTrackRequest(req.into()); - Self::forward_signal_request(req, delegate); + self.forward_signal_request(req); } local::OutputEvent::SfuUnpublishRequest(req) => { let req = proto::signal_request::Message::UnpublishDataTrackRequest(req.into()); - Self::forward_signal_request(req, delegate); + self.forward_signal_request(req); } } } - fn forward_signal_request( - message: proto::signal_request::Message, - delegate: &Arc, - ) { + fn forward_signal_request(&self, message: proto::signal_request::Message) { let req = proto::SignalRequest { message: Some(message) }.encode_to_vec(); - delegate.on_signal_request(req); + self.delegate.on_signal_request(req); } } + +async fn shutdown_forward_task(input: local::ManagerInput, token: CancellationToken) { + // TODO: consider having manager work with cancellation token out-of-the-box. + token.cancelled().await; + _ = input.send(local::InputEvent::Shutdown); +} diff --git a/livekit-uniffi/src/data_track/remote.rs b/livekit-uniffi/src/data_track/remote.rs index d6d51fde4..8c1d2aa7c 100644 --- a/livekit-uniffi/src/data_track/remote.rs +++ b/livekit-uniffi/src/data_track/remote.rs @@ -115,8 +115,11 @@ impl RemoteDataTrackManager { let manager_options = remote::ManagerOptions { decryption_provider }; let (manager, input, output) = remote::Manager::new(manager_options); - tokio::spawn(Self::shutdown_forward_task(input.clone(), token.clone())); - tokio::spawn(Self::delegate_forward_task(output, delegate, token.clone())); + tokio::spawn(shutdown_forward_task(input.clone(), token.clone())); + + let delegate_forward = DelegateForwardTask { output, delegate, token: token.clone() }; + tokio::spawn(delegate_forward.run()); + tokio::spawn(manager.run()); Self { input, _guard: token.drop_guard() }.into() @@ -178,50 +181,47 @@ impl RemoteDataTrackManager { } } -impl RemoteDataTrackManager { - async fn shutdown_forward_task(input: remote::ManagerInput, token: CancellationToken) { - // TODO: consider having manager work with cancellation token out-of-the-box. - token.cancelled().await; - _ = input.send(remote::InputEvent::Shutdown); - } +/// Task for forwarding manger output events to the foreign [`RemoteDataTrackManagerDelegate`]. +struct DelegateForwardTask { + output: remote::ManagerOutput, + delegate: Arc, + token: CancellationToken, +} - async fn delegate_forward_task( - mut output: remote::ManagerOutput, - delegate: Arc, - token: CancellationToken, - ) { +impl DelegateForwardTask { + async fn run(mut self) { loop { tokio::select! { - _ = token.cancelled() => break, - Some(event) = output.next() => Self::forward_event(event, &delegate) + _ = self.token.cancelled() => break, + Some(event) = self.output.next() => self.forward_event(event) } } } - fn forward_event( - event: remote::OutputEvent, - delegate: &Arc, - ) { + fn forward_event(&self, event: remote::OutputEvent) { match event { - remote::OutputEvent::SfuUpdateSubscription(req) => { - let req = proto::signal_request::Message::UpdateDataSubscription(req.into()); - Self::forward_signal_request(req, delegate); - } remote::OutputEvent::TrackPublished(event) => { let track = Arc::new(RemoteDataTrack(event.track)); - delegate.on_track_published(track); + self.delegate.on_track_published(track); } remote::OutputEvent::TrackUnpublished(event) => { - delegate.on_track_unpublished(event.sid) + self.delegate.on_track_unpublished(event.sid) + } + remote::OutputEvent::SfuUpdateSubscription(req) => { + let req = proto::signal_request::Message::UpdateDataSubscription(req.into()); + self.forward_signal_request(req); } } } - fn forward_signal_request( - message: proto::signal_request::Message, - delegate: &Arc, - ) { + fn forward_signal_request(&self, message: proto::signal_request::Message) { let req = proto::SignalRequest { message: Some(message) }.encode_to_vec(); - delegate.on_signal_request(req); + self.delegate.on_signal_request(req); } } + +async fn shutdown_forward_task(input: remote::ManagerInput, token: CancellationToken) { + // TODO: consider having manager work with cancellation token out-of-the-box. + token.cancelled().await; + _ = input.send(remote::InputEvent::Shutdown); +} From f2815eae21c23319b7a09e7a4076f660e8493e51 Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Tue, 21 Apr 2026 16:25:28 +0900 Subject: [PATCH 28/46] Comment --- livekit-uniffi/src/data_track/local.rs | 3 ++- livekit-uniffi/src/data_track/remote.rs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/livekit-uniffi/src/data_track/local.rs b/livekit-uniffi/src/data_track/local.rs index 3d7916554..6a2c6d313 100644 --- a/livekit-uniffi/src/data_track/local.rs +++ b/livekit-uniffi/src/data_track/local.rs @@ -228,7 +228,8 @@ impl DelegateForwardTask { } async fn shutdown_forward_task(input: local::ManagerInput, token: CancellationToken) { - // TODO: consider having manager work with cancellation token out-of-the-box. + // TODO: refactor manager to work with cancellation tokens directly, eliminating the need + // for this additional task. token.cancelled().await; _ = input.send(local::InputEvent::Shutdown); } diff --git a/livekit-uniffi/src/data_track/remote.rs b/livekit-uniffi/src/data_track/remote.rs index 8c1d2aa7c..580aeb7c0 100644 --- a/livekit-uniffi/src/data_track/remote.rs +++ b/livekit-uniffi/src/data_track/remote.rs @@ -221,7 +221,8 @@ impl DelegateForwardTask { } async fn shutdown_forward_task(input: remote::ManagerInput, token: CancellationToken) { - // TODO: consider having manager work with cancellation token out-of-the-box. + // TODO: refactor manager to work with cancellation tokens directly, eliminating the need + // for this additional task. token.cancelled().await; _ = input.send(remote::InputEvent::Shutdown); } From 91d251ce650f3c89277061b07c8233fb146794c0 Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Tue, 21 Apr 2026 16:26:52 +0900 Subject: [PATCH 29/46] Expose wait for unpublish --- livekit-uniffi/src/data_track/local.rs | 9 +++++++++ livekit-uniffi/src/data_track/remote.rs | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/livekit-uniffi/src/data_track/local.rs b/livekit-uniffi/src/data_track/local.rs index 6a2c6d313..134302bec 100644 --- a/livekit-uniffi/src/data_track/local.rs +++ b/livekit-uniffi/src/data_track/local.rs @@ -38,6 +38,15 @@ impl LocalDataTrack { self.0.is_published() } + /// Waits asynchronously until the track is unpublished. + /// + /// Use this to trigger follow-up work once the track is no longer published. + /// If the track is already unpublished, this method returns immediately. + /// + async fn wait_for_unpublish(&self) { + self.0.wait_for_unpublish().await + } + /// Information about the data track. fn info(&self) -> DataTrackInfo { self.0.info().into() diff --git a/livekit-uniffi/src/data_track/remote.rs b/livekit-uniffi/src/data_track/remote.rs index 580aeb7c0..854b91a7d 100644 --- a/livekit-uniffi/src/data_track/remote.rs +++ b/livekit-uniffi/src/data_track/remote.rs @@ -39,6 +39,15 @@ impl RemoteDataTrack { self.0.is_published() } + /// Waits asynchronously until the track is unpublished. + /// + /// Use this to trigger follow-up work once the track is no longer published. + /// If the track is already unpublished, this method returns immediately. + /// + async fn wait_for_unpublish(&self) { + self.0.wait_for_unpublish().await + } + /// Information about the data track. fn info(&self) -> DataTrackInfo { self.0.info().into() From 66d365ed98b3a33e6f6c658c788a5e906acb3a5e Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Wed, 22 Apr 2026 14:54:04 +0900 Subject: [PATCH 30/46] Move comment --- livekit-uniffi/src/data_track/local.rs | 5 +++-- livekit-uniffi/src/data_track/remote.rs | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/livekit-uniffi/src/data_track/local.rs b/livekit-uniffi/src/data_track/local.rs index 134302bec..d26577dee 100644 --- a/livekit-uniffi/src/data_track/local.rs +++ b/livekit-uniffi/src/data_track/local.rs @@ -124,6 +124,9 @@ impl LocalDataTrackManager { let manager_options = local::ManagerOptions { encryption_provider }; let (manager, input, output) = local::Manager::new(manager_options); + + // TODO: in a follow-up PR, refactor manager to work with cancellation tokens directly, eliminating the + // need for this additional task. tokio::spawn(shutdown_forward_task(input.clone(), token.clone())); let delegate_forward = DelegateForwardTask { output, delegate, token: token.clone() }; @@ -237,8 +240,6 @@ impl DelegateForwardTask { } async fn shutdown_forward_task(input: local::ManagerInput, token: CancellationToken) { - // TODO: refactor manager to work with cancellation tokens directly, eliminating the need - // for this additional task. token.cancelled().await; _ = input.send(local::InputEvent::Shutdown); } diff --git a/livekit-uniffi/src/data_track/remote.rs b/livekit-uniffi/src/data_track/remote.rs index 854b91a7d..6f7b7af72 100644 --- a/livekit-uniffi/src/data_track/remote.rs +++ b/livekit-uniffi/src/data_track/remote.rs @@ -124,6 +124,9 @@ impl RemoteDataTrackManager { let manager_options = remote::ManagerOptions { decryption_provider }; let (manager, input, output) = remote::Manager::new(manager_options); + + // TODO: in a follow-up PR, refactor manager to work with cancellation tokens directly, eliminating the + // need for this additional task. tokio::spawn(shutdown_forward_task(input.clone(), token.clone())); let delegate_forward = DelegateForwardTask { output, delegate, token: token.clone() }; @@ -230,8 +233,6 @@ impl DelegateForwardTask { } async fn shutdown_forward_task(input: remote::ManagerInput, token: CancellationToken) { - // TODO: refactor manager to work with cancellation tokens directly, eliminating the need - // for this additional task. token.cancelled().await; _ = input.send(remote::InputEvent::Shutdown); } From bd81965800428915a7c07ea3ee3d500542a9d1bb Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Wed, 22 Apr 2026 15:19:57 +0900 Subject: [PATCH 31/46] Docs --- livekit-uniffi/src/data_track/local.rs | 5 ----- livekit-uniffi/src/data_track/mod.rs | 8 ++++++++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/livekit-uniffi/src/data_track/local.rs b/livekit-uniffi/src/data_track/local.rs index d26577dee..cf1d917ef 100644 --- a/livekit-uniffi/src/data_track/local.rs +++ b/livekit-uniffi/src/data_track/local.rs @@ -89,11 +89,6 @@ pub enum PublishError { } /// System for managing data track publications. -/// -/// FFI clients construct an instance of this inside `Room`, -/// send it relevant signal responses via [`Self::handle_signal_response`], -/// and handle outputs via [`LocalDataTrackManagerDelegate`]. -/// #[derive(uniffi::Object)] struct LocalDataTrackManager { input: local::ManagerInput, diff --git a/livekit-uniffi/src/data_track/mod.rs b/livekit-uniffi/src/data_track/mod.rs index f69a4fd2a..7ede0b117 100644 --- a/livekit-uniffi/src/data_track/mod.rs +++ b/livekit-uniffi/src/data_track/mod.rs @@ -12,6 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! Data tracks core functionality from the [`livekit-datatrack`] crate. +//! +//! At a high level, FFI clients integrate this by instantiating a [`local::LocalDataTrackManager`] and a +//! [`remote::RemoteDataTrackManager`] inside their implementation of `Room`, forwarding input events and handling +//! output events. Architecturally, the managers have no dependency on WebRTC or the signaling +//! client, allowing them to be wired up to the FFI client's own implementations of these components. +//! + use bytes::Bytes; use livekit_datatrack::api::{DataTrackFrame, DataTrackSid}; From eea0ac739230f67ce7a37e1c59b591306330b9a2 Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Wed, 22 Apr 2026 15:21:44 +0900 Subject: [PATCH 32/46] Common module --- livekit-uniffi/src/data_track/common.rs | 55 +++++++++++++++++++++++++ livekit-uniffi/src/data_track/local.rs | 2 +- livekit-uniffi/src/data_track/mod.rs | 45 +------------------- livekit-uniffi/src/data_track/remote.rs | 2 +- 4 files changed, 59 insertions(+), 45 deletions(-) create mode 100644 livekit-uniffi/src/data_track/common.rs diff --git a/livekit-uniffi/src/data_track/common.rs b/livekit-uniffi/src/data_track/common.rs new file mode 100644 index 000000000..8b19639c3 --- /dev/null +++ b/livekit-uniffi/src/data_track/common.rs @@ -0,0 +1,55 @@ +// Copyright 2026 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use bytes::Bytes; +use livekit_datatrack::api::{DataTrackFrame, DataTrackSid}; + +uniffi::custom_type!(DataTrackSid, String, { + remote, + lower: |s| String::from(s), + try_lift: |s| DataTrackSid::try_from(s).map_err(|e| uniffi::deps::anyhow::anyhow!("{e}")), +}); + +#[uniffi::remote(Record)] +pub struct DataTrackFrame { + payload: Bytes, + user_timestamp: Option +} +/// Information about a published data track. +#[derive(uniffi::Record)] +pub struct DataTrackInfo { + pub sid: DataTrackSid, + pub name: String, + pub uses_e2ee: bool, +} + +impl From<&livekit_datatrack::api::DataTrackInfo> for DataTrackInfo { + fn from(info: &livekit_datatrack::api::DataTrackInfo) -> Self { + Self { sid: info.sid(), name: info.name().to_string(), uses_e2ee: info.uses_e2ee() } + } +} + +/// Signal response crossing the FFI boundary could not be processed. +#[derive(uniffi::Error, thiserror::Error, Debug)] +#[uniffi(flat_error)] +pub enum HandleSignalResponseError { + #[error("Response decoding failed: {0}")] + Decode(prost::DecodeError), + #[error("Response container has no message")] + EmptyMessage, + #[error("Unsupported response type in this context")] + UnsupportedType, + #[error(transparent)] + Internal(livekit_datatrack::api::InternalError) +} \ No newline at end of file diff --git a/livekit-uniffi/src/data_track/local.rs b/livekit-uniffi/src/data_track/local.rs index cf1d917ef..36c7630fc 100644 --- a/livekit-uniffi/src/data_track/local.rs +++ b/livekit-uniffi/src/data_track/local.rs @@ -14,7 +14,7 @@ use super::{ e2ee::{DataTrackEncryptionProvider, FfiEncryptionProvider}, - DataTrackInfo, HandleSignalResponseError, + common::{DataTrackInfo, HandleSignalResponseError}, }; use bytes::Bytes; use futures_util::StreamExt; diff --git a/livekit-uniffi/src/data_track/mod.rs b/livekit-uniffi/src/data_track/mod.rs index 7ede0b117..d146f2d55 100644 --- a/livekit-uniffi/src/data_track/mod.rs +++ b/livekit-uniffi/src/data_track/mod.rs @@ -20,48 +20,7 @@ //! client, allowing them to be wired up to the FFI client's own implementations of these components. //! -use bytes::Bytes; -use livekit_datatrack::api::{DataTrackFrame, DataTrackSid}; - +pub mod common; +pub mod e2ee; pub mod local; pub mod remote; -pub mod e2ee; - -uniffi::custom_type!(DataTrackSid, String, { - remote, - lower: |s| String::from(s), - try_lift: |s| DataTrackSid::try_from(s).map_err(|e| uniffi::deps::anyhow::anyhow!("{e}")), -}); - -#[uniffi::remote(Record)] -pub struct DataTrackFrame { - payload: Bytes, - user_timestamp: Option -} -/// Information about a published data track. -#[derive(uniffi::Record)] -pub struct DataTrackInfo { - pub sid: DataTrackSid, - pub name: String, - pub uses_e2ee: bool, -} - -impl From<&livekit_datatrack::api::DataTrackInfo> for DataTrackInfo { - fn from(info: &livekit_datatrack::api::DataTrackInfo) -> Self { - Self { sid: info.sid(), name: info.name().to_string(), uses_e2ee: info.uses_e2ee() } - } -} - -/// Signal response crossing the FFI boundary could not be processed. -#[derive(uniffi::Error, thiserror::Error, Debug)] -#[uniffi(flat_error)] -pub enum HandleSignalResponseError { - #[error("Response decoding failed: {0}")] - Decode(prost::DecodeError), - #[error("Response container has no message")] - EmptyMessage, - #[error("Unsupported response type in this context")] - UnsupportedType, - #[error(transparent)] - Internal(livekit_datatrack::api::InternalError) -} \ No newline at end of file diff --git a/livekit-uniffi/src/data_track/remote.rs b/livekit-uniffi/src/data_track/remote.rs index 6f7b7af72..974370662 100644 --- a/livekit-uniffi/src/data_track/remote.rs +++ b/livekit-uniffi/src/data_track/remote.rs @@ -14,7 +14,7 @@ use super::{ e2ee::{DataTrackDecryptionProvider, FfiDecryptionProvider}, - DataTrackInfo, HandleSignalResponseError, + common::{DataTrackInfo, HandleSignalResponseError} }; use bytes::Bytes; use futures_util::StreamExt; From d428f66d6ba5d0e3a8be8aef58f7f07dae9b587c Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Wed, 22 Apr 2026 15:23:04 +0900 Subject: [PATCH 33/46] Format --- livekit-datatrack/src/local/manager.rs | 5 +---- livekit-datatrack/src/remote/manager.rs | 5 +---- livekit-uniffi/src/data_track/common.rs | 7 ++++--- livekit-uniffi/src/data_track/local.rs | 2 +- livekit-uniffi/src/data_track/remote.rs | 2 +- 5 files changed, 8 insertions(+), 13 deletions(-) diff --git a/livekit-datatrack/src/local/manager.rs b/livekit-datatrack/src/local/manager.rs index 0d99d1826..c9196d268 100644 --- a/livekit-datatrack/src/local/manager.rs +++ b/livekit-datatrack/src/local/manager.rs @@ -413,10 +413,7 @@ pub struct ManagerOutput(ReceiverStream); impl Stream for ManagerOutput { type Item = OutputEvent; - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut TaskContext<'_>, - ) -> Poll> { + fn poll_next(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { Pin::new(&mut self.0).poll_next(cx) } } diff --git a/livekit-datatrack/src/remote/manager.rs b/livekit-datatrack/src/remote/manager.rs index e7e008083..587688153 100644 --- a/livekit-datatrack/src/remote/manager.rs +++ b/livekit-datatrack/src/remote/manager.rs @@ -452,10 +452,7 @@ pub struct ManagerOutput(ReceiverStream); impl Stream for ManagerOutput { type Item = OutputEvent; - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut TaskContext<'_>, - ) -> Poll> { + fn poll_next(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { Pin::new(&mut self.0).poll_next(cx) } } diff --git a/livekit-uniffi/src/data_track/common.rs b/livekit-uniffi/src/data_track/common.rs index 8b19639c3..5429349bb 100644 --- a/livekit-uniffi/src/data_track/common.rs +++ b/livekit-uniffi/src/data_track/common.rs @@ -24,8 +24,9 @@ uniffi::custom_type!(DataTrackSid, String, { #[uniffi::remote(Record)] pub struct DataTrackFrame { payload: Bytes, - user_timestamp: Option + user_timestamp: Option, } + /// Information about a published data track. #[derive(uniffi::Record)] pub struct DataTrackInfo { @@ -51,5 +52,5 @@ pub enum HandleSignalResponseError { #[error("Unsupported response type in this context")] UnsupportedType, #[error(transparent)] - Internal(livekit_datatrack::api::InternalError) -} \ No newline at end of file + Internal(livekit_datatrack::api::InternalError), +} diff --git a/livekit-uniffi/src/data_track/local.rs b/livekit-uniffi/src/data_track/local.rs index 36c7630fc..e3f276db4 100644 --- a/livekit-uniffi/src/data_track/local.rs +++ b/livekit-uniffi/src/data_track/local.rs @@ -13,8 +13,8 @@ // limitations under the License. use super::{ - e2ee::{DataTrackEncryptionProvider, FfiEncryptionProvider}, common::{DataTrackInfo, HandleSignalResponseError}, + e2ee::{DataTrackEncryptionProvider, FfiEncryptionProvider}, }; use bytes::Bytes; use futures_util::StreamExt; diff --git a/livekit-uniffi/src/data_track/remote.rs b/livekit-uniffi/src/data_track/remote.rs index 974370662..b43f72b4e 100644 --- a/livekit-uniffi/src/data_track/remote.rs +++ b/livekit-uniffi/src/data_track/remote.rs @@ -13,8 +13,8 @@ // limitations under the License. use super::{ + common::{DataTrackInfo, HandleSignalResponseError}, e2ee::{DataTrackDecryptionProvider, FfiDecryptionProvider}, - common::{DataTrackInfo, HandleSignalResponseError} }; use bytes::Bytes; use futures_util::StreamExt; From 7a35faf430e93a7c266a3b3a49ebf00a5848de2b Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Wed, 22 Apr 2026 15:35:13 +0900 Subject: [PATCH 34/46] Consistent access control --- livekit-uniffi/src/data_track/common.rs | 4 ++-- livekit-uniffi/src/data_track/local.rs | 10 +++++----- livekit-uniffi/src/data_track/remote.rs | 14 +++++++------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/livekit-uniffi/src/data_track/common.rs b/livekit-uniffi/src/data_track/common.rs index 5429349bb..3bf197fd5 100644 --- a/livekit-uniffi/src/data_track/common.rs +++ b/livekit-uniffi/src/data_track/common.rs @@ -23,8 +23,8 @@ uniffi::custom_type!(DataTrackSid, String, { #[uniffi::remote(Record)] pub struct DataTrackFrame { - payload: Bytes, - user_timestamp: Option, + pub payload: Bytes, + pub user_timestamp: Option, } /// Information about a published data track. diff --git a/livekit-uniffi/src/data_track/local.rs b/livekit-uniffi/src/data_track/local.rs index e3f276db4..53b5a6355 100644 --- a/livekit-uniffi/src/data_track/local.rs +++ b/livekit-uniffi/src/data_track/local.rs @@ -34,7 +34,7 @@ pub struct LocalDataTrack(DataTrack); #[uniffi::export] impl LocalDataTrack { /// Whether or not the track is currently published. - fn is_published(&self) -> bool { + pub fn is_published(&self) -> bool { self.0.is_published() } @@ -43,24 +43,24 @@ impl LocalDataTrack { /// Use this to trigger follow-up work once the track is no longer published. /// If the track is already unpublished, this method returns immediately. /// - async fn wait_for_unpublish(&self) { + pub async fn wait_for_unpublish(&self) { self.0.wait_for_unpublish().await } /// Information about the data track. - fn info(&self) -> DataTrackInfo { + pub fn info(&self) -> DataTrackInfo { self.0.info().into() } /// Try pushing a frame to subscribers of the track. - fn try_push(&self, frame: DataTrackFrame) -> Result<(), PushFrameErrorReason> { + pub fn try_push(&self, frame: DataTrackFrame) -> Result<(), PushFrameErrorReason> { // `PushFrameError` returns ownership of the unpublished frame to the caller; // since this isn't applicable in an FFI context, just provide the reason. self.0.try_push(frame).map_err(|err| err.reason()) } /// Unpublishes the track. - fn unpublish(&self) { + pub fn unpublish(&self) { self.0.unpublish(); } } diff --git a/livekit-uniffi/src/data_track/remote.rs b/livekit-uniffi/src/data_track/remote.rs index b43f72b4e..4493f50f7 100644 --- a/livekit-uniffi/src/data_track/remote.rs +++ b/livekit-uniffi/src/data_track/remote.rs @@ -35,7 +35,7 @@ pub struct RemoteDataTrack(DataTrack); #[uniffi::export] impl RemoteDataTrack { /// Whether or not the track is currently published. - fn is_published(&self) -> bool { + pub fn is_published(&self) -> bool { self.0.is_published() } @@ -44,22 +44,22 @@ impl RemoteDataTrack { /// Use this to trigger follow-up work once the track is no longer published. /// If the track is already unpublished, this method returns immediately. /// - async fn wait_for_unpublish(&self) { + pub async fn wait_for_unpublish(&self) { self.0.wait_for_unpublish().await } /// Information about the data track. - fn info(&self) -> DataTrackInfo { + pub fn info(&self) -> DataTrackInfo { self.0.info().into() } /// Identity of the participant who published the track. - fn publisher_identity(&self) -> String { + pub fn publisher_identity(&self) -> String { self.0.publisher_identity().to_string() } /// Subscribes to the data track. - async fn subscribe(&self) -> Result { + pub async fn subscribe(&self) -> Result { self.0.subscribe().await.map(|stream| DataTrackStream(Mutex::new(stream))) } } @@ -75,11 +75,11 @@ pub enum DataTrackSubscribeError { /// A stream of [`DataTrackFrame`]s received from a [`RemoteDataTrack`]. #[derive(uniffi::Object)] -struct DataTrackStream(Mutex); +pub struct DataTrackStream(Mutex); #[uniffi::export] impl DataTrackStream { - async fn next(&self) -> Option { + pub async fn next(&self) -> Option { // TODO: avoid mutex? self.0.try_lock().unwrap().next().await } From 8c39c412b34dc2f652ed3589086cccabdd09ffb3 Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Wed, 22 Apr 2026 15:42:17 +0900 Subject: [PATCH 35/46] Doc --- livekit-uniffi/src/data_track/remote.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/livekit-uniffi/src/data_track/remote.rs b/livekit-uniffi/src/data_track/remote.rs index 4493f50f7..85fff8204 100644 --- a/livekit-uniffi/src/data_track/remote.rs +++ b/livekit-uniffi/src/data_track/remote.rs @@ -79,6 +79,7 @@ pub struct DataTrackStream(Mutex); #[uniffi::export] impl DataTrackStream { + /// Returns the next received frame or `None` if the subscription has ended. pub async fn next(&self) -> Option { // TODO: avoid mutex? self.0.try_lock().unwrap().next().await From bea25604c888046fb91ac25cd73ab9501c72dc83 Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Wed, 22 Apr 2026 15:49:50 +0900 Subject: [PATCH 36/46] Add enum case --- livekit-datatrack/src/e2ee.rs | 12 ++++++++---- livekit-uniffi/src/data_track/e2ee.rs | 8 ++++++-- livekit/src/room/e2ee/data_track.rs | 8 ++++---- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/livekit-datatrack/src/e2ee.rs b/livekit-datatrack/src/e2ee.rs index b286eb115..701cd13fd 100644 --- a/livekit-datatrack/src/e2ee.rs +++ b/livekit-datatrack/src/e2ee.rs @@ -31,13 +31,17 @@ pub struct EncryptedPayload { /// An error indicating a payload could not be encrypted. #[derive(Debug, Error)] -#[error("Encryption failed")] -pub enum EncryptionError {} +pub enum EncryptionError { + #[error("Encryption failed")] + Failed, +} /// An error indicating a payload could not be decrypted. #[derive(Debug, Error)] -#[error("Decryption failed")] -pub enum DecryptionError {} +pub enum DecryptionError { + #[error("Decryption failed")] + Failed, +} /// Provider for encrypting payloads for E2EE. pub trait EncryptionProvider: Send + Sync + Debug { diff --git a/livekit-uniffi/src/data_track/e2ee.rs b/livekit-uniffi/src/data_track/e2ee.rs index fecf4edb1..9ef35e3e6 100644 --- a/livekit-uniffi/src/data_track/e2ee.rs +++ b/livekit-uniffi/src/data_track/e2ee.rs @@ -22,11 +22,15 @@ use std::sync::Arc; #[uniffi::remote(Error)] #[uniffi(flat_error)] -pub enum EncryptionError {} +pub enum EncryptionError { + Failed, +} #[uniffi::remote(Error)] #[uniffi(flat_error)] -pub enum DecryptionError {} +pub enum DecryptionError { + Failed, +} #[uniffi::remote(Record)] pub struct EncryptedPayload { diff --git a/livekit/src/room/e2ee/data_track.rs b/livekit/src/room/e2ee/data_track.rs index 068819ecc..96bdb4fc6 100644 --- a/livekit/src/room/e2ee/data_track.rs +++ b/livekit/src/room/e2ee/data_track.rs @@ -37,7 +37,7 @@ impl dt::EncryptionProvider for DataTrackEncryptionProvider { let encrypted = self .manager .encrypt_data(payload.into(), &self.sender_identity, key_index) - .map_err(|_| dt::EncryptionError)?; + .map_err(|_| dt::EncryptionError::Failed)?; debug_assert_eq!( encrypted.key_index as u32, @@ -46,8 +46,8 @@ impl dt::EncryptionProvider for DataTrackEncryptionProvider { ); let payload = encrypted.data.into(); - let iv = encrypted.iv.try_into().map_err(|_| dt::EncryptionError)?; - let key_index = encrypted.key_index.try_into().map_err(|_| dt::EncryptionError)?; + let iv = encrypted.iv.try_into().map_err(|_| dt::EncryptionError::Failed)?; + let key_index = encrypted.key_index.try_into().map_err(|_| dt::EncryptionError::Failed)?; Ok(dt::EncryptedPayload { payload, iv, key_index }) } @@ -79,7 +79,7 @@ impl dt::DecryptionProvider for DataTrackDecryptionProvider { payload.key_index as u32, sender_identity, ) - .ok_or_else(|| dt::DecryptionError)?; + .ok_or_else(|| dt::DecryptionError::Failed)?; Ok(Bytes::from(decrypted)) } } From 1db92e3a96090fbc700399682224680ccd1e15ae Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Wed, 22 Apr 2026 15:57:09 +0900 Subject: [PATCH 37/46] Changeset --- .changeset/make_data_track_e2ee_errors_enums.md | 6 ++++++ .changeset/make_data_track_frame_fields_public.md | 6 ++++++ .changeset/uniffi_data_tracks.md | 5 +++++ 3 files changed, 17 insertions(+) create mode 100644 .changeset/make_data_track_e2ee_errors_enums.md create mode 100644 .changeset/make_data_track_frame_fields_public.md create mode 100644 .changeset/uniffi_data_tracks.md diff --git a/.changeset/make_data_track_e2ee_errors_enums.md b/.changeset/make_data_track_e2ee_errors_enums.md new file mode 100644 index 000000000..ac8853123 --- /dev/null +++ b/.changeset/make_data_track_e2ee_errors_enums.md @@ -0,0 +1,6 @@ +--- +livekit: patch +livekit-datatrack: patch +--- + +# Make data track E2EE errors enums diff --git a/.changeset/make_data_track_frame_fields_public.md b/.changeset/make_data_track_frame_fields_public.md new file mode 100644 index 000000000..e9ce6d22e --- /dev/null +++ b/.changeset/make_data_track_frame_fields_public.md @@ -0,0 +1,6 @@ +--- +livekit-datatrack: patch +livekit: patch +--- + +# Make data track frame fields public diff --git a/.changeset/uniffi_data_tracks.md b/.changeset/uniffi_data_tracks.md new file mode 100644 index 000000000..d3abfea92 --- /dev/null +++ b/.changeset/uniffi_data_tracks.md @@ -0,0 +1,5 @@ +--- +livekit-uniffi: minor +--- + +# Expose data tracks core functionality From bf80cf4dd8bf65ca987febf1118a4ae7b747445c Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Wed, 22 Apr 2026 15:57:40 +0900 Subject: [PATCH 38/46] Fix typos --- livekit-uniffi/src/data_track/local.rs | 2 +- livekit-uniffi/src/data_track/remote.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/livekit-uniffi/src/data_track/local.rs b/livekit-uniffi/src/data_track/local.rs index 53b5a6355..5f7d32d38 100644 --- a/livekit-uniffi/src/data_track/local.rs +++ b/livekit-uniffi/src/data_track/local.rs @@ -195,7 +195,7 @@ impl LocalDataTrackManager { } } -/// Task for forwarding manger output events to the foreign [`LocalDataTrackManagerDelegate`]. +/// Task for forwarding manager output events to the foreign [`LocalDataTrackManagerDelegate`]. struct DelegateForwardTask { output: local::ManagerOutput, delegate: Arc, diff --git a/livekit-uniffi/src/data_track/remote.rs b/livekit-uniffi/src/data_track/remote.rs index 85fff8204..ad5f5466e 100644 --- a/livekit-uniffi/src/data_track/remote.rs +++ b/livekit-uniffi/src/data_track/remote.rs @@ -28,7 +28,7 @@ use std::sync::Arc; use tokio::sync::Mutex; use tokio_util::sync::{CancellationToken, DropGuard}; -/// Data track published by the local participant. +/// Data track published by the remote participant. #[derive(uniffi::Object)] pub struct RemoteDataTrack(DataTrack); @@ -194,7 +194,7 @@ impl RemoteDataTrackManager { } } -/// Task for forwarding manger output events to the foreign [`RemoteDataTrackManagerDelegate`]. +/// Task for forwarding manager output events to the foreign [`RemoteDataTrackManagerDelegate`]. struct DelegateForwardTask { output: remote::ManagerOutput, delegate: Arc, From 4079e771e826b2398f076fb3fd49b79f7e29ad9f Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Wed, 22 Apr 2026 15:58:01 +0900 Subject: [PATCH 39/46] Remove unnecessary async marker --- livekit-uniffi/src/data_track/local.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit-uniffi/src/data_track/local.rs b/livekit-uniffi/src/data_track/local.rs index 5f7d32d38..746e67ea5 100644 --- a/livekit-uniffi/src/data_track/local.rs +++ b/livekit-uniffi/src/data_track/local.rs @@ -153,7 +153,7 @@ impl LocalDataTrackManager { /// This must be invoked after a full reconnect in order for existing publications /// to be recognized by the SFU. Each republished track will be assigned a new SID. /// - pub async fn republish_tracks(&self) { + pub fn republish_tracks(&self) { _ = self.input.send(local::InputEvent::RepublishTracks); } From 69b5b03850cbec46f994ef348796bd2af60a9edb Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Wed, 22 Apr 2026 15:59:26 +0900 Subject: [PATCH 40/46] Use lock instead of try_lock --- livekit-uniffi/src/data_track/remote.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit-uniffi/src/data_track/remote.rs b/livekit-uniffi/src/data_track/remote.rs index ad5f5466e..a951c8e3a 100644 --- a/livekit-uniffi/src/data_track/remote.rs +++ b/livekit-uniffi/src/data_track/remote.rs @@ -82,7 +82,7 @@ impl DataTrackStream { /// Returns the next received frame or `None` if the subscription has ended. pub async fn next(&self) -> Option { // TODO: avoid mutex? - self.0.try_lock().unwrap().next().await + self.0.lock().await.next().await } } From def2f971d95b84e692c8111b2f6a15c3a2f4977a Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Wed, 22 Apr 2026 16:01:48 +0900 Subject: [PATCH 41/46] Doc --- livekit-uniffi/src/data_track/local.rs | 2 ++ livekit-uniffi/src/data_track/remote.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/livekit-uniffi/src/data_track/local.rs b/livekit-uniffi/src/data_track/local.rs index 746e67ea5..22d8a0b40 100644 --- a/livekit-uniffi/src/data_track/local.rs +++ b/livekit-uniffi/src/data_track/local.rs @@ -165,6 +165,8 @@ impl LocalDataTrackManager { /// - `RequestResponse` /// - `PublishDataTrackResponse` /// + /// If a signal response type not listed above is provided, the result is an error. + /// pub fn handle_signal_response(&self, res: &[u8]) -> Result<(), HandleSignalResponseError> { let res = proto::SignalResponse::decode(res) .map_err(|err| HandleSignalResponseError::Decode(err))?; diff --git a/livekit-uniffi/src/data_track/remote.rs b/livekit-uniffi/src/data_track/remote.rs index a951c8e3a..cd975fa5b 100644 --- a/livekit-uniffi/src/data_track/remote.rs +++ b/livekit-uniffi/src/data_track/remote.rs @@ -155,6 +155,8 @@ impl RemoteDataTrackManager { /// - `ParticipantUpdate` /// - `DataTrackSubscriberHandles` /// + /// If a signal response type not listed above is provided, the result is an error. + /// /// Note: the local participant identity is required to exclude data tracks published by the /// local participant from being treated as remote tracks. /// From 0533a38789ff043b751a8b55dd17bbdfe2c0b2f3 Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Wed, 22 Apr 2026 16:02:05 +0900 Subject: [PATCH 42/46] Cargo lock --- Cargo.lock | 888 ++++++++++++++++++++++++----------------------------- 1 file changed, 404 insertions(+), 484 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5129c97de..bce24f0b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -120,7 +120,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" dependencies = [ "alsa-sys", - "bitflags 2.11.0", + "bitflags 2.11.1", "cfg-if 1.0.4", "libc", ] @@ -137,23 +137,21 @@ dependencies = [ [[package]] name = "android-activity" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" +checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" dependencies = [ "android-properties", - "bitflags 2.11.0", + "bitflags 2.11.1", "cc", - "cesu8", - "jni 0.21.1", - "jni-sys 0.3.1", + "jni 0.22.4", "libc", "log", "ndk 0.9.0", "ndk-context", "ndk-sys 0.6.0+11769913", "num_enum", - "thiserror 1.0.69", + "thiserror 2.0.18", ] [[package]] @@ -362,7 +360,7 @@ dependencies = [ "memchr", "proc-macro2", "quote", - "rustc-hash 2.1.1", + "rustc-hash 2.1.2", "serde", "serde_derive", "syn 2.0.117", @@ -411,8 +409,8 @@ checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" dependencies = [ "async-task", "concurrent-queue", - "fastrand 2.3.0", - "futures-lite 2.6.1", + "fastrand", + "futures-lite", "pin-project-lite", "slab", ] @@ -428,7 +426,7 @@ dependencies = [ "async-io", "async-lock", "blocking", - "futures-lite 2.6.1", + "futures-lite", "once_cell", ] @@ -442,9 +440,9 @@ dependencies = [ "cfg-if 1.0.4", "concurrent-queue", "futures-io", - "futures-lite 2.6.1", + "futures-lite", "parking", - "polling 3.11.0", + "polling", "rustix 1.1.4", "slab", "windows-sys 0.61.2", @@ -487,7 +485,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-io", - "futures-lite 2.6.1", + "futures-lite", "gloo-timers", "kv-log-macro", "log", @@ -742,7 +740,7 @@ version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cexpr", "clang-sys", "itertools 0.13.0", @@ -751,7 +749,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash 2.1.1", + "rustc-hash 2.1.2", "shlex", "syn 2.0.117", ] @@ -811,20 +809,20 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" dependencies = [ "serde_core", ] [[package]] name = "bitstream-io" -version = "4.9.0" +version = "4.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60d4bd9d1db2c6bdf285e223a7fa369d5ce98ec767dec949c6ca62863ce61757" +checksum = "7eff00be299a18769011411c9def0d827e8f2d7bf0c3dbf53633147a8867fd1f" dependencies = [ - "core2", + "no_std_io2", ] [[package]] @@ -869,7 +867,7 @@ dependencies = [ "async-channel 2.5.0", "async-task", "futures-io", - "futures-lite 2.6.1", + "futures-lite", "piper", ] @@ -959,9 +957,9 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "log", - "polling 3.11.0", + "polling", "rustix 0.38.44", "slab", "thiserror 1.0.69", @@ -973,8 +971,8 @@ version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7" dependencies = [ - "bitflags 2.11.0", - "polling 3.11.0", + "bitflags 2.11.1", + "polling", "rustix 1.1.4", "slab", "tracing", @@ -1038,15 +1036,18 @@ dependencies = [ [[package]] name = "castaway" -version = "0.1.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] [[package]] name = "cc" -version = "1.2.57" +version = "1.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" dependencies = [ "find-msvc-tools", "jobserver", @@ -1133,9 +1134,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", @@ -1155,9 +1156,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -1201,7 +1202,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81411967c50ee9a1fc11365f8c585f863a22a9697c89239c452292c40ba79b0d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block", "core-foundation 0.10.1", "core-graphics-types 0.2.0", @@ -1412,7 +1413,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation 0.10.1", "libc", ] @@ -1442,15 +1443,6 @@ dependencies = [ "objc", ] -[[package]] -name = "core2" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" -dependencies = [ - "memchr", -] - [[package]] name = "coreaudio-rs" version = "0.11.3" @@ -1576,9 +1568,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.6.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "424e0138278faeb2b401f174ad17e715c829512d74f3d1e81eb43365c2e0590e" +checksum = "95d0d11eb38e7642efca359c3cf6eb7b2e528182d09110165de70192b0352775" dependencies = [ "ctor-proc-macro", "dtor", @@ -1586,9 +1578,9 @@ dependencies = [ [[package]] name = "ctor-proc-macro" -version = "0.0.7" +version = "0.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" +checksum = "a7ab264ea985f1bd27887d7b21ea2bb046728e05d11909ca138d700c494730db" [[package]] name = "curl" @@ -1607,9 +1599,9 @@ dependencies = [ [[package]] name = "curl-sys" -version = "0.4.86+curl-8.19.0" +version = "0.4.87+curl-8.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1dd6a487cf4532ce0d801634b82aa2deb7c9c3ed930b9dadfce904df000745" +checksum = "61a460380f0ef783703dcbe909107f39c162adeac050d73c850055118b5b6327" dependencies = [ "cc", "libc", @@ -1676,7 +1668,7 @@ checksum = "b0f4697d190a142477b16aef7da8a99bfdc41e7e8b1687583c0d23a79c7afc1e" dependencies = [ "cc", "codespan-reporting 0.13.1", - "indexmap 2.13.0", + "indexmap 2.14.0", "proc-macro2", "quote", "scratch", @@ -1691,7 +1683,7 @@ checksum = "d0956799fa8678d4c50eed028f2de1c0552ae183c76e976cf7ca8c4e36a7c328" dependencies = [ "clap", "codespan-reporting 0.13.1", - "indexmap 2.13.0", + "indexmap 2.14.0", "proc-macro2", "quote", "syn 2.0.117", @@ -1709,7 +1701,7 @@ version = "1.0.194" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6acc6b5822b9526adfb4fc377b67128fdd60aac757cc4a741a6278603f763cf" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.14.0", "proc-macro2", "quote", "syn 2.0.117", @@ -1883,7 +1875,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "objc2 0.6.4", ] @@ -1930,18 +1922,18 @@ checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" [[package]] name = "dtor" -version = "0.1.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "404d02eeb088a82cfd873006cb713fe411306c7d182c344905e101fb1167d301" +checksum = "17f72721db8027a4e96dd6fb50d2a1d32259c9d3da1b63dee612ccd981e14293" dependencies = [ "dtor-proc-macro", ] [[package]] name = "dtor-proc-macro" -version = "0.0.6" +version = "0.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" +checksum = "8c98b077c7463d01d22dde8a24378ddf1ca7263dc687cffbed38819ea6c21131" [[package]] name = "dummy" @@ -2060,7 +2052,7 @@ checksum = "6a9b567d356674e9a5121ed3fedfb0a7c31e059fe71f6972b691bcd0bfc284e3" dependencies = [ "accesskit", "ahash", - "bitflags 2.11.0", + "bitflags 2.11.1", "emath", "epaint", "log", @@ -2334,23 +2326,14 @@ dependencies = [ "deunicode", "dummy", "either", - "rand 0.9.2", + "rand 0.9.4", ] [[package]] name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - -[[package]] -name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "fax" @@ -2605,28 +2588,13 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand 1.9.0", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - [[package]] name = "futures-lite" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ - "fastrand 2.3.0", + "fastrand", "futures-core", "futures-io", "parking", @@ -2736,9 +2704,9 @@ dependencies = [ [[package]] name = "gif" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5df2ba84018d80c213569363bdcd0c64e6933c67fe4c1d60ecf822971a3c35e" +checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" dependencies = [ "color_quant", "weezl", @@ -2780,7 +2748,7 @@ version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16de123c2e6c90ce3b573b7330de19be649080ec612033d397d72da265f1bd8b" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "futures-channel", "futures-core", "futures-executor", @@ -2885,7 +2853,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "gpu-alloc-types", ] @@ -2895,7 +2863,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -2930,7 +2898,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "gpu-descriptor-types", "hashbrown 0.15.5", ] @@ -2941,7 +2909,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -2967,7 +2935,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.13.0", + "indexmap 2.14.0", "slab", "tokio", "tokio-util", @@ -3021,6 +2989,12 @@ dependencies = [ "foldhash 0.2.0", ] +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + [[package]] name = "hdrhistogram" version = "7.5.4" @@ -3190,9 +3164,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ "atomic-waker", "bytes", @@ -3203,7 +3177,6 @@ dependencies = [ "httparse", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -3211,20 +3184,19 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ "http 1.4.0", - "hyper 1.8.1", + "hyper 1.9.0", "hyper-util", - "rustls 0.23.37", + "rustls 0.23.38", "rustls-native-certs 0.8.3", - "rustls-pki-types", "tokio", "tokio-rustls 0.26.4", "tower-service", - "webpki-roots 1.0.6", + "webpki-roots 1.0.7", ] [[package]] @@ -3247,7 +3219,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.8.1", + "hyper 1.9.0", "hyper-util", "native-tls", "tokio", @@ -3267,7 +3239,7 @@ dependencies = [ "futures-util", "http 1.4.0", "http-body 1.0.1", - "hyper 1.8.1", + "hyper 1.9.0", "ipnet", "libc", "percent-encoding", @@ -3304,12 +3276,13 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -3317,9 +3290,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -3330,9 +3303,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -3344,15 +3317,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -3364,15 +3337,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -3475,12 +3448,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "serde", "serde_core", ] @@ -3494,15 +3467,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if 1.0.4", -] - [[package]] name = "interpolate_name" version = "0.2.4" @@ -3522,9 +3486,9 @@ checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "iri-string" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e7418f59cc01c88316161279a7f665217ae316b388e58a0d10e29f54f1e5eb" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" dependencies = [ "memchr", "serde", @@ -3538,23 +3502,22 @@ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "isahc" -version = "1.7.2" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" +checksum = "036cdb58c0a12db9e7162af193785bf9b6797f7b52035ff03f91c591619fde67" dependencies = [ - "async-channel 1.9.0", + "async-channel 2.5.0", "castaway", "crossbeam-utils", "curl", "curl-sys", "encoding_rs", - "event-listener 2.5.3", - "futures-lite 1.13.0", + "event-listener 5.4.1", + "futures-lite", "http 0.2.12", "log", "mime", - "once_cell", - "polling 2.8.0", + "polling", "serde", "serde_json", "slab", @@ -3726,10 +3689,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.91" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" dependencies = [ + "cfg-if 1.0.4", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -3747,7 +3712,7 @@ dependencies = [ "js-sys", "p256", "p384", - "rand 0.8.5", + "rand 0.8.6", "rsa", "serde", "serde_json", @@ -3820,9 +3785,9 @@ checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" [[package]] name = "libc" -version = "0.2.183" +version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" [[package]] name = "libfuzzer-sys" @@ -3862,14 +3827,14 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.14" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "libc", "plain", - "redox_syscall 0.7.3", + "redox_syscall 0.7.4", ] [[package]] @@ -3899,9 +3864,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.25" +version = "1.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52f4c29e2a68ac30c9087e1b772dc9f44a2b66ed44edf2266cf2be9b03dafc1" +checksum = "fc3a226e576f50782b3305c5ccf458698f92798987f551c6a02efe8276721e22" dependencies = [ "cc", "libc", @@ -3986,9 +3951,9 @@ dependencies = [ [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "litrs" @@ -4043,7 +4008,7 @@ dependencies = [ "parking_lot", "pbjson-types", "prost 0.12.6", - "rand 0.9.2", + "rand 0.9.4", "reqwest", "rustls-native-certs 0.6.3", "scopeguard", @@ -4070,7 +4035,7 @@ dependencies = [ "livekit-protocol", "livekit-runtime", "log", - "rand 0.9.2", + "rand 0.9.4", "test-case", "thiserror 2.0.18", "tokio", @@ -4146,6 +4111,7 @@ dependencies = [ "log", "once_cell", "prost 0.12.6", + "thiserror 2.0.18", "tokio", "tokio-util", "uniffi", @@ -4335,7 +4301,7 @@ version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00c15a6f673ff72ddcc22394663290f870fb224c1bfce55734a75c414150e605" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block", "core-graphics-types 0.2.0", "foreign-types 0.5.0", @@ -4350,7 +4316,7 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7047791b5bc903b8cd963014b355f71dc9864a9a0b727057676c1dcae5cbc15" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block", "core-graphics-types 0.2.0", "foreign-types 0.5.0", @@ -4383,9 +4349,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "wasi", @@ -4431,14 +4397,14 @@ checksum = "066cf25f0e8b11ee0df221219010f213ad429855f57c494f995590c861a9a7d8" dependencies = [ "arrayvec", "bit-set 0.8.0", - "bitflags 2.11.0", + "bitflags 2.11.1", "cfg-if 1.0.4", "cfg_aliases", "codespan-reporting 0.12.0", "half", "hashbrown 0.16.1", "hexf-parse", - "indexmap 2.13.0", + "indexmap 2.14.0", "libm", "log", "num-traits", @@ -4457,14 +4423,14 @@ checksum = "618f667225063219ddfc61251087db8a9aec3c3f0950c916b614e403486f1135" dependencies = [ "arrayvec", "bit-set 0.8.0", - "bitflags 2.11.0", + "bitflags 2.11.1", "cfg-if 1.0.4", "cfg_aliases", "codespan-reporting 0.12.0", "half", "hashbrown 0.16.1", "hexf-parse", - "indexmap 2.13.0", + "indexmap 2.14.0", "libm", "log", "num-traits", @@ -4486,17 +4452,17 @@ dependencies = [ [[package]] name = "napi" -version = "3.8.3" +version = "3.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6944d0bf100571cd6e1a98a316cdca262deb6fccf8d93f5ae1502ca3fc88bd3" +checksum = "fa73b028610e2b26e9e40bd2c8ff8a98e6d7ed5d67d89ebf4bfd2f992616b024" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "ctor", "futures", "napi-build", "napi-sys", "nohash-hasher", - "rustc-hash 2.1.1", + "rustc-hash 2.1.2", "tokio", ] @@ -4508,9 +4474,9 @@ checksum = "d376940fd5b723c6893cd1ee3f33abbfd86acb1cd1ec079f3ab04a2a3bc4d3b1" [[package]] name = "napi-derive" -version = "3.5.2" +version = "3.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c914b5e420182bfb73504e0607592cdb8e2e21437d450883077669fb72a114d" +checksum = "7430702d3cc05cf55f0a2c9e41d991c3b7a53f91e6146a8f282b1bfc7f3fd133" dependencies = [ "convert_case", "ctor", @@ -4522,9 +4488,9 @@ dependencies = [ [[package]] name = "napi-derive-backend" -version = "5.0.2" +version = "5.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0864cf6a82e2cfb69067374b64c9253d7e910e5b34db833ed7495dda56ccb18" +checksum = "1ca5a083f2c9b49a0c7d33ec75c083498849c6fcc46f5497317faa39ea77f5d5" dependencies = [ "convert_case", "proc-macro2", @@ -4595,7 +4561,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "jni-sys 0.3.1", "log", "ndk-sys 0.5.0+25.2.9519653", @@ -4609,7 +4575,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "jni-sys 0.3.1", "log", "ndk-sys 0.6.0+11769913", @@ -4654,12 +4620,21 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cfg-if 1.0.4", "cfg_aliases", "libc", ] +[[package]] +name = "no_std_io2" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b51ed7824b6e07d354605f4abb3d9d300350701299da96642ee084f5ce631550" +dependencies = [ + "memchr", +] + [[package]] name = "nohash-hasher" version = "0.2.0" @@ -4796,7 +4771,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand 0.8.5", + "rand 0.8.6", "smallvec", "zeroize", ] @@ -4812,9 +4787,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-derive" @@ -4931,7 +4906,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2 0.5.1", "libc", "objc2 0.5.2", @@ -4947,7 +4922,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "objc2 0.6.4", "objc2-core-graphics", "objc2-foundation 0.3.2", @@ -4959,7 +4934,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2 0.5.1", "objc2 0.5.2", "objc2-core-location 0.2.2", @@ -4972,7 +4947,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "objc2 0.6.4", "objc2-foundation 0.3.2", ] @@ -4994,7 +4969,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -5016,7 +4991,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "dispatch2", "objc2 0.6.4", ] @@ -5027,7 +5002,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "dispatch2", "objc2 0.6.4", "objc2-core-foundation", @@ -5084,7 +5059,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "objc2 0.6.4", "objc2-core-foundation", "objc2-core-graphics", @@ -5102,7 +5077,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2 0.5.1", "dispatch", "libc", @@ -5115,7 +5090,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2 0.6.2", "libc", "objc2 0.6.4", @@ -5128,7 +5103,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "objc2 0.6.4", "objc2-core-foundation", ] @@ -5151,7 +5126,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -5163,7 +5138,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -5176,7 +5151,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "objc2 0.6.4", "objc2-core-foundation", "objc2-foundation 0.3.2", @@ -5198,7 +5173,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2 0.5.1", "objc2 0.5.2", "objc2-cloud-kit 0.2.2", @@ -5219,7 +5194,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2 0.6.2", "objc2 0.6.4", "objc2-cloud-kit 0.3.2", @@ -5251,7 +5226,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2 0.5.1", "objc2 0.5.2", "objc2-core-location 0.2.2", @@ -5323,11 +5298,11 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "openssl" -version = "0.10.76" +version = "0.10.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" +checksum = "f38c4372413cdaaf3cc79dd92d29d7d9f5ab09b51b10dded508fb90bb70b9222" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cfg-if 1.0.4", "foreign-types 0.3.2", "libc", @@ -5361,18 +5336,18 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-src" -version = "300.5.5+3.5.5" +version = "300.6.0+3.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f1787d533e03597a7934fd0a765f0d28e94ecc5fb7789f8053b1e699a56f709" +checksum = "a8e8cbfd3a4a8c8f089147fd7aaa33cf8c7450c4d09f8f80698a0cf093abeff4" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.112" +version = "0.9.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +checksum = "13ce1245cd07fcc4cfdb438f7507b0c7e4f3849a69fd84d52374c66d83741bb6" dependencies = [ "cc", "libc", @@ -5383,9 +5358,9 @@ dependencies = [ [[package]] name = "orbclient" -version = "0.3.51" +version = "0.3.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59aed3b33578edcfa1bc96a321d590d31832b6ad55a26f0313362ce687e9abd6" +checksum = "12c6933ddbbd16539a7672e697bb8d41ac3a4e99ac43eeb40c07236bd7fcb2dd" dependencies = [ "libc", "libredox", @@ -5393,9 +5368,9 @@ dependencies = [ [[package]] name = "ordered-float" -version = "5.1.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d" +checksum = "b7d950ca161dc355eaf28f82b11345ed76c6e1f6eb1f4f4479e0323b9e2fbd0e" dependencies = [ "num-traits", ] @@ -5652,7 +5627,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset 0.4.2", - "indexmap 2.13.0", + "indexmap 2.14.0", ] [[package]] @@ -5663,7 +5638,7 @@ checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ "fixedbitset 0.5.7", "hashbrown 0.15.5", - "indexmap 2.13.0", + "indexmap 2.14.0", ] [[package]] @@ -5705,7 +5680,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" dependencies = [ "atomic-waker", - "fastrand 2.3.0", + "fastrand", "futures-io", ] @@ -5732,9 +5707,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "plain" @@ -5759,29 +5734,13 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "crc32fast", "fdeflate", "flate2", "miniz_oxide", ] -[[package]] -name = "polling" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if 1.0.4", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys 0.48.0", -] - [[package]] name = "polling" version = "3.11.0" @@ -5810,18 +5769,18 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" dependencies = [ "portable-atomic", ] [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -6050,9 +6009,9 @@ dependencies = [ [[package]] name = "pxfm" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d" +checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" [[package]] name = "qoi" @@ -6089,8 +6048,8 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.1.1", - "rustls 0.23.37", + "rustc-hash 2.1.2", + "rustls 0.23.38", "socket2 0.6.3", "thiserror 2.0.18", "tokio", @@ -6107,10 +6066,10 @@ dependencies = [ "bytes", "getrandom 0.3.4", "lru-slab", - "rand 0.9.2", + "rand 0.9.4", "ring", - "rustc-hash 2.1.1", - "rustls 0.23.37", + "rustc-hash 2.1.2", + "rustls 0.23.38", "rustls-pki-types", "slab", "thiserror 2.0.18", @@ -6156,9 +6115,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -6167,9 +6126,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -6220,7 +6179,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" dependencies = [ "num-traits", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -6256,7 +6215,7 @@ dependencies = [ "num-traits", "paste", "profiling", - "rand 0.9.2", + "rand 0.9.4", "rand_chacha 0.9.0", "simd_helpers", "thiserror 2.0.18", @@ -6293,9 +6252,9 @@ checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" [[package]] name = "rayon" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -6326,16 +6285,16 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] name = "redox_syscall" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" +checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -6387,7 +6346,7 @@ dependencies = [ "http 1.4.0", "http-body 1.0.1", "http-body-util", - "hyper 1.8.1", + "hyper 1.9.0", "hyper-rustls", "hyper-tls", "hyper-util", @@ -6397,7 +6356,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.37", + "rustls 0.23.38", "rustls-native-certs 0.8.3", "rustls-pki-types", "serde", @@ -6414,7 +6373,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 1.0.6", + "webpki-roots 1.0.7", ] [[package]] @@ -6460,7 +6419,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db09040cc89e461f1a265139777a2bde7f8d8c67c4936f700c63ce3e2904d468" dependencies = [ "base64 0.22.1", - "bitflags 2.11.0", + "bitflags 2.11.1", "serde", "serde_derive", "unicode-ident", @@ -6474,7 +6433,7 @@ dependencies = [ "livekit", "livekit-api", "log", - "rand 0.9.2", + "rand 0.9.4", "serde_json", "tokio", ] @@ -6533,9 +6492,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustc_version" @@ -6566,7 +6525,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "errno", "libc", "linux-raw-sys 0.4.15", @@ -6579,7 +6538,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "errno", "libc", "linux-raw-sys 0.12.1", @@ -6600,14 +6559,14 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.37" +version = "0.23.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" dependencies = [ "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.10", + "rustls-webpki 0.103.13", "subtle", "zeroize", ] @@ -6667,9 +6626,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.10" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "ring", "rustls-pki-types", @@ -6828,7 +6787,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation 0.9.4", "core-foundation-sys 0.8.7", "libc", @@ -6841,7 +6800,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation 0.10.1", "core-foundation-sys 0.8.7", "libc", @@ -6860,9 +6819,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" dependencies = [ "serde", "serde_core", @@ -6877,7 +6836,7 @@ dependencies = [ "env_logger 0.11.10", "livekit", "log", - "rand 0.9.2", + "rand 0.9.4", "tokio", ] @@ -6926,9 +6885,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876ac351060d4f882bb1032b6369eb0aef79ad9df1ea8bc404874d8cc3d0cd98" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] @@ -7004,9 +6963,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "simd_cesu8" @@ -7056,11 +7015,11 @@ dependencies = [ [[package]] name = "sluice" -version = "0.5.5" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" +checksum = "160b744a45e8261307bcfe03c98e2f8274502207d534c9a64b675c4db1b6bd58" dependencies = [ - "async-channel 1.9.0", + "async-channel 2.5.0", "futures-core", "futures-io", ] @@ -7083,7 +7042,7 @@ version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "calloop 0.13.0", "calloop-wayland-source 0.3.0", "cursor-icon", @@ -7108,7 +7067,7 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "calloop 0.14.4", "calloop-wayland-source 0.4.1", "cursor-icon", @@ -7192,7 +7151,7 @@ version = "0.3.0+sdk-1.3.268.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -7308,14 +7267,14 @@ dependencies = [ [[package]] name = "system-deps" -version = "7.0.7" +version = "7.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c8f33736f986f16d69b6cb8b03f55ddcad5c41acc4ccc39dd88e84aa805e7f" +checksum = "396a35feb67335377e0251fcbc1092fc85c484bd4e3a7a54319399da127796e7" dependencies = [ "cfg-expr", "heck 0.5.0", "pkg-config", - "toml", + "toml 1.1.2+spec-1.1.0", "version-compare", ] @@ -7342,7 +7301,7 @@ version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ - "fastrand 2.3.0", + "fastrand", "getrandom 0.4.2", "once_cell", "rustix 1.1.4", @@ -7393,9 +7352,9 @@ dependencies = [ [[package]] name = "test-log" -version = "0.2.19" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d53ac171c92a39e4769491c4b4dde7022c60042254b5fc044ae409d34a24d4" +checksum = "2f46bf474f0a4afebf92f076d54fd5e63423d9438b8c278a3d2ccb0f47f7cdb3" dependencies = [ "env_logger 0.11.10", "test-log-macros", @@ -7403,16 +7362,26 @@ dependencies = [ ] [[package]] -name = "test-log-macros" -version = "0.2.19" +name = "test-log-core" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be35209fd0781c5401458ab66e4f98accf63553e8fae7425503e92fdd319783b" +checksum = "37d4d41320b48bc4a211a9021678fcc0c99569b594ea31c93735b8e517102b4c" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] +[[package]] +name = "test-log-macros" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9beb9249a81e430dffd42400a49019bcf548444f1968ff23080a625de0d4d320" +dependencies = [ + "syn 2.0.117", + "test-log-core", +] + [[package]] name = "textwrap" version = "0.16.2" @@ -7543,9 +7512,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -7568,9 +7537,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.50.0" +version = "1.52.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" dependencies = [ "bytes", "libc", @@ -7596,9 +7565,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", @@ -7631,7 +7600,7 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.37", + "rustls 0.23.38", "tokio", ] @@ -7684,7 +7653,7 @@ version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.14.0", "serde_core", "serde_spanned", "toml_datetime 0.7.5+spec-1.1.0", @@ -7693,6 +7662,21 @@ dependencies = [ "winnow 0.7.15", ] +[[package]] +name = "toml" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +dependencies = [ + "indexmap 2.14.0", + "serde_core", + "serde_spanned", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.2", +] + [[package]] name = "toml_datetime" version = "0.7.5+spec-1.1.0" @@ -7704,39 +7688,39 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.1.0+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.25.8+spec-1.1.0" +version = "0.25.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16bff38f1d86c47f9ff0647e6838d7bb362522bdf44006c7068c2b1e606f1f3c" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" dependencies = [ - "indexmap 2.13.0", - "toml_datetime 1.1.0+spec-1.1.0", + "indexmap 2.14.0", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 1.0.0", + "winnow 1.0.2", ] [[package]] name = "toml_parser" -version = "1.1.0+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.0", + "winnow 1.0.2", ] [[package]] name = "toml_writer" -version = "1.1.0+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d282ade6016312faf3e41e57ebbba0c073e4056dab1232ab1cb624199648f8ed" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "tonic" @@ -7777,7 +7761,7 @@ dependencies = [ "indexmap 1.9.3", "pin-project", "pin-project-lite", - "rand 0.8.5", + "rand 0.8.6", "slab", "tokio", "tokio-util", @@ -7807,7 +7791,7 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "bytes", "futures-util", "http 1.4.0", @@ -8040,7 +8024,7 @@ checksum = "87561bf0b84f74a124afc0f1997682728da6cd821083511e0357432954fd24f6" dependencies = [ "getrandom 0.2.17", "log", - "rand 0.8.5", + "rand 0.8.6", "rand_distr", "rustfft", "tract-nnef", @@ -8081,7 +8065,7 @@ dependencies = [ "httparse", "log", "native-tls", - "rand 0.8.5", + "rand 0.8.6", "rustls 0.21.12", "sha1", "thiserror 1.0.69", @@ -8102,7 +8086,7 @@ dependencies = [ "httparse", "log", "native-tls", - "rand 0.8.5", + "rand 0.8.6", "sha1", "thiserror 1.0.69", "url", @@ -8115,14 +8099,14 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" dependencies = [ - "rustc-hash 2.1.1", + "rustc-hash 2.1.2", ] [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "ucd-trie" @@ -8147,9 +8131,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-width" @@ -8194,12 +8178,12 @@ dependencies = [ "glob", "goblin", "heck 0.5.0", - "indexmap 2.13.0", + "indexmap 2.14.0", "once_cell", "serde", "tempfile", "textwrap", - "toml", + "toml 0.9.12+spec-1.1.0", "uniffi_internal_macros", "uniffi_meta", "uniffi_pipeline", @@ -8236,7 +8220,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c2a6f93e7b73726e2015696ece25ca0ac5a5f1cf8d6a7ab5214dd0a01d2edf" dependencies = [ "anyhow", - "indexmap 2.13.0", + "indexmap 2.14.0", "proc-macro2", "quote", "syn 2.0.117", @@ -8255,7 +8239,7 @@ dependencies = [ "quote", "serde", "syn 2.0.117", - "toml", + "toml 0.9.12+spec-1.1.0", "uniffi_meta", ] @@ -8279,7 +8263,7 @@ checksum = "8c27c4b515d25f8e53cc918e238c39a79c3144a40eaf2e51c4a7958973422c29" dependencies = [ "anyhow", "heck 0.5.0", - "indexmap 2.13.0", + "indexmap 2.14.0", "tempfile", "uniffi_internal_macros", ] @@ -8426,11 +8410,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", ] [[package]] @@ -8439,14 +8423,14 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] name = "wasm-bindgen" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" dependencies = [ "cfg-if 1.0.4", "once_cell", @@ -8457,23 +8441,19 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.64" +version = "0.4.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" +checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" dependencies = [ - "cfg-if 1.0.4", - "futures-util", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -8481,9 +8461,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" dependencies = [ "bumpalo", "proc-macro2", @@ -8494,9 +8474,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" dependencies = [ "unicode-ident", ] @@ -8518,7 +8498,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap 2.13.0", + "indexmap 2.14.0", "wasm-encoder", "wasmparser", ] @@ -8529,17 +8509,17 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "hashbrown 0.15.5", - "indexmap 2.13.0", + "indexmap 2.14.0", "semver", ] [[package]] name = "wayland-backend" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa75f400b7f719bcd68b3f47cd939ba654cedeef690f486db71331eec4c6a406" +checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d" dependencies = [ "cc", "downcast-rs", @@ -8551,11 +8531,11 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.13" +version = "0.31.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab51d9f7c071abeee76007e2b742499e535148035bb835f97aaed1338cf516c3" +checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "rustix 1.1.4", "wayland-backend", "wayland-scanner", @@ -8567,16 +8547,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cursor-icon", "wayland-backend", ] [[package]] name = "wayland-cursor" -version = "0.31.13" +version = "0.31.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b3298683470fbdc6ca40151dfc48c8f2fd4c41a26e13042f801f85002384091" +checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d" dependencies = [ "rustix 1.1.4", "wayland-client", @@ -8585,11 +8565,11 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.32.11" +version = "0.32.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b23b5df31ceff1328f06ac607591d5ba360cf58f90c8fad4ac8d3a55a3c4aec7" +checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "wayland-backend", "wayland-client", "wayland-scanner", @@ -8601,7 +8581,7 @@ version = "20250721.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "wayland-backend", "wayland-client", "wayland-protocols", @@ -8610,11 +8590,11 @@ dependencies = [ [[package]] name = "wayland-protocols-misc" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "429b99200febaf95d4f4e46deff6fe4382bcff3280ee16a41cf887b3c3364984" +checksum = "6e9567599ef23e09b8dad6e429e5738d4509dfc46b3b21f32841a304d16b29c8" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "wayland-backend", "wayland-client", "wayland-protocols", @@ -8623,11 +8603,11 @@ dependencies = [ [[package]] name = "wayland-protocols-plasma" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d392fc283a87774afc9beefcd6f931582bb97fe0e6ced0b306a62cb1d026527c" +checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "wayland-backend", "wayland-client", "wayland-protocols", @@ -8636,11 +8616,11 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78248e4cc0eff8163370ba5c158630dcae1f3497a586b826eca2ef5f348d6235" +checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "wayland-backend", "wayland-client", "wayland-protocols", @@ -8649,9 +8629,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.9" +version = "0.31.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c86287151a309799b821ca709b7345a048a2956af05957c89cb824ab919fa4e3" +checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a" dependencies = [ "proc-macro2", "quick-xml", @@ -8660,9 +8640,9 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.31.10" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374f6b70e8e0d6bf9461a32988fd553b59ff630964924dad6e4a4eb6bd538d17" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" dependencies = [ "dlib", "log", @@ -8672,9 +8652,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.91" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" dependencies = [ "js-sys", "wasm-bindgen", @@ -8692,9 +8672,9 @@ dependencies = [ [[package]] name = "webbrowser" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe985f41e291eecef5e5c0770a18d28390addb03331c043964d9e916453d6f16" +checksum = "0fc95580916af1e68ff6a7be07446fc5db73ebf71cf092de939bbf5f7e189f72" dependencies = [ "core-foundation 0.10.1", "jni 0.22.4", @@ -8723,9 +8703,9 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webpki-roots" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" dependencies = [ "rustls-pki-types", ] @@ -8779,7 +8759,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfe68bac7cde125de7a731c3400723cadaaf1703795ad3f4805f187459cd7a77" dependencies = [ "arrayvec", - "bitflags 2.11.0", + "bitflags 2.11.1", "cfg-if 1.0.4", "cfg_aliases", "document-features", @@ -8808,7 +8788,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9cb534d5ffd109c7d1135f34cdae29e60eab94855a625dcfe1705f8bc7ad79f" dependencies = [ "arrayvec", - "bitflags 2.11.0", + "bitflags 2.11.1", "bytemuck", "cfg-if 1.0.4", "cfg_aliases", @@ -8840,12 +8820,12 @@ dependencies = [ "arrayvec", "bit-set 0.8.0", "bit-vec 0.8.0", - "bitflags 2.11.0", + "bitflags 2.11.1", "bytemuck", "cfg_aliases", "document-features", "hashbrown 0.16.1", - "indexmap 2.13.0", + "indexmap 2.14.0", "log", "naga 27.0.3", "once_cell", @@ -8872,12 +8852,12 @@ dependencies = [ "arrayvec", "bit-set 0.8.0", "bit-vec 0.8.0", - "bitflags 2.11.0", + "bitflags 2.11.1", "bytemuck", "cfg_aliases", "document-features", "hashbrown 0.16.1", - "indexmap 2.13.0", + "indexmap 2.14.0", "log", "naga 28.0.0", "once_cell", @@ -8959,7 +8939,7 @@ dependencies = [ "arrayvec", "ash", "bit-set 0.8.0", - "bitflags 2.11.0", + "bitflags 2.11.1", "block", "bytemuck", "cfg-if 1.0.4", @@ -9008,7 +8988,7 @@ dependencies = [ "arrayvec", "ash", "bit-set 0.8.0", - "bitflags 2.11.0", + "bitflags 2.11.1", "block", "bytemuck", "cfg-if 1.0.4", @@ -9052,7 +9032,7 @@ version = "27.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afdcf84c395990db737f2dd91628706cb31e86d72e53482320d368e52b5da5eb" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "bytemuck", "js-sys", "log", @@ -9066,7 +9046,7 @@ version = "28.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e18308757e594ed2cd27dddbb16a139c42a683819d32a2e0b1b0167552f5840c" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "bytemuck", "js-sys", "log", @@ -9340,15 +9320,6 @@ dependencies = [ "windows-targets 0.42.2", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -9400,21 +9371,6 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -9463,12 +9419,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -9487,12 +9437,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -9511,12 +9455,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -9547,12 +9485,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -9571,12 +9503,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -9595,12 +9521,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -9619,12 +9539,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -9646,7 +9560,7 @@ dependencies = [ "ahash", "android-activity", "atomic-waker", - "bitflags 2.11.0", + "bitflags 2.11.1", "block2 0.5.1", "bytemuck", "calloop 0.13.0", @@ -9700,9 +9614,9 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" dependencies = [ "memchr", ] @@ -9716,6 +9630,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" @@ -9735,7 +9655,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck 0.5.0", - "indexmap 2.13.0", + "indexmap 2.14.0", "prettyplease", "syn 2.0.117", "wasm-metadata", @@ -9765,8 +9685,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.0", - "indexmap 2.13.0", + "bitflags 2.11.1", + "indexmap 2.14.0", "log", "serde", "serde_derive", @@ -9785,7 +9705,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap 2.13.0", + "indexmap 2.14.0", "log", "semver", "serde", @@ -9797,9 +9717,9 @@ dependencies = [ [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "x11-dl" @@ -9855,7 +9775,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "dlib", "log", "once_cell", @@ -9882,9 +9802,9 @@ checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -9893,9 +9813,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -9916,18 +9836,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.47" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.47" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", @@ -9936,18 +9856,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -9963,9 +9883,9 @@ checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -9974,9 +9894,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -9985,9 +9905,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", @@ -10066,9 +9986,9 @@ dependencies = [ [[package]] name = "zune-jpeg" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7a1c0af6e5d8d1363f4994b7a091ccf963d8b694f7da5b0b9cceb82da2c0a6" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" dependencies = [ "zune-core", ] From 9f89c44adfe7269febc9dea24f7334acbc557247 Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Wed, 22 Apr 2026 16:03:41 +0900 Subject: [PATCH 43/46] Changeset --- ...e_concrete_type_for_data_track_manager_output_events.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/use_concrete_type_for_data_track_manager_output_events.md diff --git a/.changeset/use_concrete_type_for_data_track_manager_output_events.md b/.changeset/use_concrete_type_for_data_track_manager_output_events.md new file mode 100644 index 000000000..1edb95e10 --- /dev/null +++ b/.changeset/use_concrete_type_for_data_track_manager_output_events.md @@ -0,0 +1,7 @@ +--- +livekit-datatrack: patch +livekit: patch +livekit-ffi: patch +--- + +# Use concrete type for data track manager output events From 90cf95ac0d8aa5a043da11671054bf84d0125e5e Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Wed, 22 Apr 2026 18:20:36 +0900 Subject: [PATCH 44/46] Async runtime --- livekit-uniffi/Cargo.toml | 2 +- livekit-uniffi/src/data_track/local.rs | 8 ++++--- livekit-uniffi/src/data_track/remote.rs | 8 ++++--- livekit-uniffi/src/lib.rs | 3 +++ livekit-uniffi/src/runtime.rs | 28 +++++++++++++++++++++++++ 5 files changed, 42 insertions(+), 7 deletions(-) create mode 100644 livekit-uniffi/src/runtime.rs diff --git a/livekit-uniffi/Cargo.toml b/livekit-uniffi/Cargo.toml index 383d6b186..4386ed85c 100644 --- a/livekit-uniffi/Cargo.toml +++ b/livekit-uniffi/Cargo.toml @@ -17,7 +17,7 @@ livekit-api = { workspace = true } livekit-datatrack = { workspace = true } uniffi = { version = "0.30.0", features = ["cli", "scaffolding-ffi-buffer-fns"] } log = { workspace = true } -tokio = { workspace = true, features = ["sync"] } +tokio = { workspace = true, features = ["sync", "rt-multi-thread"] } tokio-util = "0.7.18" prost = "0.12" futures-util = { workspace = true, default-features = false, features = ["sink"] } diff --git a/livekit-uniffi/src/data_track/local.rs b/livekit-uniffi/src/data_track/local.rs index 22d8a0b40..eb1f7f1a3 100644 --- a/livekit-uniffi/src/data_track/local.rs +++ b/livekit-uniffi/src/data_track/local.rs @@ -120,14 +120,16 @@ impl LocalDataTrackManager { let (manager, input, output) = local::Manager::new(manager_options); + let rt = crate::runtime::runtime(); + // TODO: in a follow-up PR, refactor manager to work with cancellation tokens directly, eliminating the // need for this additional task. - tokio::spawn(shutdown_forward_task(input.clone(), token.clone())); + rt.spawn(shutdown_forward_task(input.clone(), token.clone())); let delegate_forward = DelegateForwardTask { output, delegate, token: token.clone() }; - tokio::spawn(delegate_forward.run()); + rt.spawn(delegate_forward.run()); - tokio::spawn(manager.run()); + rt.spawn(manager.run()); Self { input, _guard: token.drop_guard() }.into() } diff --git a/livekit-uniffi/src/data_track/remote.rs b/livekit-uniffi/src/data_track/remote.rs index cd975fa5b..74836b532 100644 --- a/livekit-uniffi/src/data_track/remote.rs +++ b/livekit-uniffi/src/data_track/remote.rs @@ -126,14 +126,16 @@ impl RemoteDataTrackManager { let (manager, input, output) = remote::Manager::new(manager_options); + let rt = crate::runtime::runtime(); + // TODO: in a follow-up PR, refactor manager to work with cancellation tokens directly, eliminating the // need for this additional task. - tokio::spawn(shutdown_forward_task(input.clone(), token.clone())); + rt.spawn(shutdown_forward_task(input.clone(), token.clone())); let delegate_forward = DelegateForwardTask { output, delegate, token: token.clone() }; - tokio::spawn(delegate_forward.run()); + rt.spawn(delegate_forward.run()); - tokio::spawn(manager.run()); + rt.spawn(manager.run()); Self { input, _guard: token.drop_guard() }.into() } diff --git a/livekit-uniffi/src/lib.rs b/livekit-uniffi/src/lib.rs index d2030dab1..94f2cc0d0 100644 --- a/livekit-uniffi/src/lib.rs +++ b/livekit-uniffi/src/lib.rs @@ -27,4 +27,7 @@ pub mod build_info; /// Shared exports and utilities. pub mod common; +/// Global async runtime. +pub mod runtime; + uniffi::setup_scaffolding!(); diff --git a/livekit-uniffi/src/runtime.rs b/livekit-uniffi/src/runtime.rs new file mode 100644 index 000000000..676dc2579 --- /dev/null +++ b/livekit-uniffi/src/runtime.rs @@ -0,0 +1,28 @@ +// Copyright 2026 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::sync::OnceLock; +use tokio::runtime::Runtime; + +/// Returns the process-global Tokio runtime, initializing it on first use. +pub(crate) fn runtime() -> &'static Runtime { + static RUNTIME: OnceLock = OnceLock::new(); + RUNTIME.get_or_init(|| { + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .thread_name("livekit-uniffi") + .build() + .expect("Failed to build livekit-uniffi tokio runtime") + }) +} From cdc2a7e4c4e9d6330eebb965720df71ab536c07e Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Wed, 22 Apr 2026 18:21:25 +0900 Subject: [PATCH 45/46] Changeset --- ...track_frame_fields_public.md => data_track_public_fields.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .changeset/{make_data_track_frame_fields_public.md => data_track_public_fields.md} (51%) diff --git a/.changeset/make_data_track_frame_fields_public.md b/.changeset/data_track_public_fields.md similarity index 51% rename from .changeset/make_data_track_frame_fields_public.md rename to .changeset/data_track_public_fields.md index e9ce6d22e..512a98ced 100644 --- a/.changeset/make_data_track_frame_fields_public.md +++ b/.changeset/data_track_public_fields.md @@ -3,4 +3,4 @@ livekit-datatrack: patch livekit: patch --- -# Make data track frame fields public +# Make some fields public for data track types From b44930608fbf929581f8436885e094af5ce672fd Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Wed, 29 Apr 2026 15:18:48 +0900 Subject: [PATCH 46/46] Separate handlers per signal response type --- livekit-uniffi/src/data_track/common.rs | 11 +++++ livekit-uniffi/src/data_track/local.rs | 58 +++++++++++-------------- livekit-uniffi/src/data_track/remote.rs | 58 +++++++++++-------------- 3 files changed, 63 insertions(+), 64 deletions(-) diff --git a/livekit-uniffi/src/data_track/common.rs b/livekit-uniffi/src/data_track/common.rs index 3bf197fd5..54c3ba813 100644 --- a/livekit-uniffi/src/data_track/common.rs +++ b/livekit-uniffi/src/data_track/common.rs @@ -14,6 +14,8 @@ use bytes::Bytes; use livekit_datatrack::api::{DataTrackFrame, DataTrackSid}; +use livekit_protocol as proto; +use prost::Message; uniffi::custom_type!(DataTrackSid, String, { remote, @@ -54,3 +56,12 @@ pub enum HandleSignalResponseError { #[error(transparent)] Internal(livekit_datatrack::api::InternalError), } + +/// Deserializes a signal response crossing the FFI boundary, returning the message variant. +pub(crate) fn deserialize_signal_response( + res: &[u8], +) -> Result { + let res = + proto::SignalResponse::decode(res).map_err(|err| HandleSignalResponseError::Decode(err))?; + res.message.ok_or(HandleSignalResponseError::EmptyMessage) +} diff --git a/livekit-uniffi/src/data_track/local.rs b/livekit-uniffi/src/data_track/local.rs index eb1f7f1a3..c3fe58e1a 100644 --- a/livekit-uniffi/src/data_track/local.rs +++ b/livekit-uniffi/src/data_track/local.rs @@ -13,7 +13,7 @@ // limitations under the License. use super::{ - common::{DataTrackInfo, HandleSignalResponseError}, + common::{deserialize_signal_response, DataTrackInfo, HandleSignalResponseError}, e2ee::{DataTrackEncryptionProvider, FfiEncryptionProvider}, }; use bytes::Bytes; @@ -159,44 +159,38 @@ impl LocalDataTrackManager { _ = self.input.send(local::InputEvent::RepublishTracks); } - /// Handles a serialized signal response from the SFU. - /// - /// This must be invoked for the following response types in order for the - /// manager to function properly: - /// - /// - `RequestResponse` - /// - `PublishDataTrackResponse` - /// - /// If a signal response type not listed above is provided, the result is an error. - /// - pub fn handle_signal_response(&self, res: &[u8]) -> Result<(), HandleSignalResponseError> { - let res = proto::SignalResponse::decode(res) - .map_err(|err| HandleSignalResponseError::Decode(err))?; - - let msg = res.message.ok_or(HandleSignalResponseError::EmptyMessage)?; - - use proto::signal_response::Message; - let publish_res = match msg { - Message::RequestResponse(msg) => { - let Some(res) = local::publish_result_from_request_response(&msg) else { - // Not from data track publish request. - return Ok(()); - }; - res - } - Message::PublishDataTrackResponse(res) => { - let res: local::SfuPublishResponse = - res.try_into().map_err(|err| HandleSignalResponseError::Internal(err))?; - res - } - _ => return Err(HandleSignalResponseError::UnsupportedType), + /// Handles a serialized `RequestResponse` signal response from the SFU. + pub fn handle_sfu_request_response(&self, res: &[u8]) -> Result<(), HandleSignalResponseError> { + let proto::signal_response::Message::RequestResponse(msg) = + deserialize_signal_response(res)? + else { + return Err(HandleSignalResponseError::UnsupportedType); }; + let Some(publish_res) = local::publish_result_from_request_response(&msg) else { + // Not from data track publish request. + return Ok(()); + }; let event: local::InputEvent = publish_res.into(); _ = self.input.send(event); Ok(()) } + + /// Handles a serialized `PublishDataTrackResponse` signal response from the SFU. + pub fn handle_sfu_publish_response(&self, res: &[u8]) -> Result<(), HandleSignalResponseError> { + let proto::signal_response::Message::PublishDataTrackResponse(msg) = + deserialize_signal_response(res)? + else { + return Err(HandleSignalResponseError::UnsupportedType); + }; + + let event: local::SfuPublishResponse = + msg.try_into().map_err(|err| HandleSignalResponseError::Internal(err))?; + _ = self.input.send(event.into()); + + Ok(()) + } } /// Task for forwarding manager output events to the foreign [`LocalDataTrackManagerDelegate`]. diff --git a/livekit-uniffi/src/data_track/remote.rs b/livekit-uniffi/src/data_track/remote.rs index 74836b532..f62cee824 100644 --- a/livekit-uniffi/src/data_track/remote.rs +++ b/livekit-uniffi/src/data_track/remote.rs @@ -13,7 +13,7 @@ // limitations under the License. use super::{ - common::{DataTrackInfo, HandleSignalResponseError}, + common::{deserialize_signal_response, DataTrackInfo, HandleSignalResponseError}, e2ee::{DataTrackDecryptionProvider, FfiDecryptionProvider}, }; use bytes::Bytes; @@ -149,46 +149,40 @@ impl RemoteDataTrackManager { _ = self.input.send(remote::InputEvent::ResendSubscriptionUpdates); } - /// Handles a serialized signal response from the SFU. - /// - /// This must be invoked for the following response types in order for the - /// manager to function properly: - /// - /// - `ParticipantUpdate` - /// - `DataTrackSubscriberHandles` - /// - /// If a signal response type not listed above is provided, the result is an error. + /// Handles a serialized `ParticipantUpdate` signal response from the SFU. /// /// Note: the local participant identity is required to exclude data tracks published by the /// local participant from being treated as remote tracks. /// - pub fn handle_signal_response( + pub fn handle_sfu_participant_update( &self, res: &[u8], local_participant_identity: String, ) -> Result<(), HandleSignalResponseError> { - let res = proto::SignalResponse::decode(res) - .map_err(|err| HandleSignalResponseError::Decode(err))?; - - let msg = res.message.ok_or(HandleSignalResponseError::EmptyMessage)?; - - use proto::signal_response::Message; - match msg { - Message::Update(mut msg) => { - let event = - remote::event_from_participant_update(&mut msg, &local_participant_identity) - .map_err(|err| HandleSignalResponseError::Internal(err))?; - _ = self.input.send(event.into()); - } - Message::DataTrackSubscriberHandles(msg) => { - let event: remote::SfuSubscriberHandles = - msg.try_into().map_err(|err| HandleSignalResponseError::Internal(err))?; - _ = self.input.send(event.into()) - } - _ => { - return Err(HandleSignalResponseError::UnsupportedType); - } + let proto::signal_response::Message::Update(mut msg) = deserialize_signal_response(res)? + else { + return Err(HandleSignalResponseError::UnsupportedType); + }; + + let event = remote::event_from_participant_update(&mut msg, &local_participant_identity) + .map_err(|err| HandleSignalResponseError::Internal(err))?; + _ = self.input.send(event.into()); + + Ok(()) + } + + /// Handles a serialized `DataTrackSubscriberHandles` signal response from the SFU. + pub fn handle_subscriber_handles(&self, res: &[u8]) -> Result<(), HandleSignalResponseError> { + let proto::signal_response::Message::DataTrackSubscriberHandles(msg) = + deserialize_signal_response(res)? + else { + return Err(HandleSignalResponseError::UnsupportedType); }; + + let event: remote::SfuSubscriberHandles = + msg.try_into().map_err(|err| HandleSignalResponseError::Internal(err))?; + _ = self.input.send(event.into()); + Ok(()) }