Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
452024f
feat: add initial livekit-common implementation
1egoman Jul 2, 2026
6904329
fix: remove serde from livekit-common
1egoman Jul 2, 2026
2400b0f
refactor: update more livekit crate references to point to livekit-co…
1egoman Jul 2, 2026
4d0c8f4
refactor: move data stream code out into new livekit-data-stream crate
1egoman Jul 6, 2026
2168702
fix: remove orphaned take_if_raw method
1egoman Jul 2, 2026
7a5f865
fix: adjust comments and docs
1egoman Jul 6, 2026
4492d6e
docs: update data streams readme
1egoman Jul 6, 2026
4abf39b
fix: address crate level visibility of data streams data structures
1egoman Jul 7, 2026
0172bfb
fix: re-export StreamWriter trait from livekit-data-stream so livekit…
1egoman Jul 7, 2026
5ecee43
chore: add changeset for data-stream crate extraction
1egoman Jul 7, 2026
433b85d
feat: migrate enum_dispatch into livekit-common
1egoman Jul 8, 2026
1a0b979
fix: ensure new_for_test is public and exposed when the test-utils fe…
1egoman Jul 8, 2026
136124f
test: add v1 data-stream unit tests (incoming + outgoing legacy path)
1egoman Jul 8, 2026
ee02f26
refactor: drop unused ClientCapability, RemoteParticipantRegistry, an…
1egoman Jul 8, 2026
4445b08
fix: run cargo fmt
1egoman Jul 8, 2026
8e956b4
refactor: merge livekit-data-stream __e2e-test feature into test-utils
1egoman Jul 8, 2026
9141c9b
Update livekit-data-stream/src/lib.rs
1egoman Jul 8, 2026
bf24f85
docs: add livekit-common AGENTS.md describing crate scope
1egoman Jul 8, 2026
d2ae930
fix: hoist doc attr macro
1egoman Jul 8, 2026
34661ad
Merge branch 'main' into data-streams-refactor
1egoman Jul 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/refactor_extract_data_stream_crates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
livekit: patch
livekit-ffi: patch
---

refactor: extract data-stream logic and shared types into new `livekit-common` and `livekit-data-stream` crates (public API unchanged; types are re-exported from `livekit`)
51 changes: 50 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ members = [
"livekit",
"livekit-api",
"livekit-protocol",
"livekit-common",
"livekit-data-stream",
Comment thread
1egoman marked this conversation as resolved.
"livekit-ffi",
"livekit-uniffi",
"livekit-datatrack",
Expand Down Expand Up @@ -50,6 +52,8 @@ livekit = { version = "0.7.51", path = "livekit" }
livekit-api = { version = "0.5.5", path = "livekit-api" }
livekit-ffi = { version = "0.12.69", path = "livekit-ffi" }
livekit-datatrack = { version = "0.1.10", path = "livekit-datatrack" }
livekit-common = { version = "0.1.0", path = "livekit-common" }
livekit-data-stream = { version = "0.1.0", path = "livekit-data-stream" }
livekit-protocol = { version = "0.7.10", path = "livekit-protocol" }
# default-features off so each consumer selects its runtime explicitly
# (livekit-runtime/tokio | /async | /dispatcher); otherwise the default `tokio`
Expand Down
1 change: 1 addition & 0 deletions livekit-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ __rustls-tls = ["tokio-tungstenite?/__rustls-tls", "reqwest?/__rustls"]

[dependencies]
livekit-protocol = { workspace = true }
livekit-common = { workspace = true }
thiserror = { workspace = true }
serde = { workspace = true, features = ["derive"] }
sha2 = "0.10"
Expand Down
6 changes: 1 addition & 5 deletions livekit-api/src/signal_client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,7 @@
const CLIENT_CAPABILITIES: &[proto::client_info::Capability] =
&[proto::client_info::Capability::CapPacketTrailer];

/// Default value for `ClientInfo.client_protocol` when a participant has not
/// advertised one (treat as v1-only / no data-stream RPC support).
pub const CLIENT_PROTOCOL_DEFAULT: i32 = 0;
/// `ClientInfo.client_protocol` value indicating support for RPC v2 over data streams.
pub const CLIENT_PROTOCOL_DATA_STREAM_RPC: i32 = 1;
pub use livekit_common::{CLIENT_PROTOCOL_DATA_STREAM_RPC, CLIENT_PROTOCOL_DEFAULT};

/// The client protocol which is sent to other clients and indicates the set of apis that other
/// clients should assume this client supports.
Expand Down Expand Up @@ -1017,7 +1013,7 @@
/// in `send`. The stream slot is None so any actual write would be dropped,
/// which is fine — these tests only assert which side of the queue each
/// message lands on.
fn make_stub_inner() -> Arc<SignalInner> {

Check warning on line 1016 in livekit-api/src/signal_client/mod.rs

View workflow job for this annotation

GitHub Actions / livekit-api (signal-client (async), cargo test -p livekit-api --no-default-features --features si...

function `make_stub_inner` is never used
Arc::new(SignalInner {
stream: AsyncRwLock::new(None),
token: Mutex::new(String::new()),
Expand Down
30 changes: 30 additions & 0 deletions livekit-common/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# AGENTS.md

## Architectural overview

- Holds foundational, broadly-shared items used across multiple LiveKit crates (e.g. [`livekit`](../livekit/), [`livekit-api`](../livekit-api/), [`livekit-data-stream`](../livekit-data-stream/))
- Internal crate — not for direct consumption by developers (public APIs live in the [`livekit`](../livekit/) crate)
- Exists purely to avoid duplication and circular dependencies: an item needed by two or more downstream crates lives here instead of in any single one
- Current contents set the bar for what fits: `ParticipantIdentity` (newtype), `EncryptionType` (enum + proto conversions), the `CLIENT_PROTOCOL_*` constants, and the `enum_dispatch!` macro

## What belongs here

- Small, self-contained, foundational items shared by **two or more** downstream crates:
- Newtypes and plain data enums (plus their `From`/`TryFrom` conversions)
- Simple constants
- Trivial, stateless helper functions and declarative macros
- Every addition must be dependency-light (see Dependencies) and free of feature/business logic

## What does NOT belong here

- Feature or business logic — keep it in the feature's own crate (e.g. `livekit-data-stream`) or in `livekit`
- Items used by only **one** crate — leave them in that crate until a second consumer actually needs them; do not hoist here speculatively
- Stateful components — managers, actors, services, or anything holding runtime state
- Wire/protocol types — those belong in `livekit-protocol` (this crate depends on it, never the reverse)
- Anything that would require a heavy or environment-specific dependency (see Dependencies)

## Dependencies

- Keep the dependency list minimal — today it is only `livekit-protocol`
- A dependency added here is forced onto **every** downstream crate; treat any new dependency as a red flag and justify it explicitly
- Never pull in heavy or environment-specific deps (`libwebrtc`, an async runtime, networking, etc.) — a type that needs those belongs in a higher-level crate
11 changes: 11 additions & 0 deletions livekit-common/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "livekit-common"
description = "Common foundational types shared across LiveKit crates"
version = "0.1.0"
readme = "README.md"
license.workspace = true
edition.workspace = true
repository.workspace = true

[dependencies]
livekit-protocol = { workspace = true }
6 changes: 6 additions & 0 deletions livekit-common/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# LiveKit Common

An internal crate which holds shared data structures that many downstream modules all use, like
`ParticipantIdentity` or `EncryptionType`.

To build applications with LiveKit, please use the public APIs provided by the [livekit](../livekit) crate.
56 changes: 56 additions & 0 deletions livekit-common/src/enum_dispatch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// 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.

/// Generates methods on an enum that forward each call to the inner value of every variant.
///
/// Given a list of variants and a set of method signatures, this expands to a `match` over `self`
/// that dispatches to the identically-named method on each variant's inner type, saving the
/// boilerplate of writing one `match` arm per variant per method.
///
/// ```ignore
/// impl AnyStreamInfo {
/// enum_dispatch!(
/// [Byte, Text];
/// pub fn id(self: &Self) -> &str;
/// pub fn total_length(self: &Self) -> Option<u64>;
/// );
/// }
/// ```
// TODO(theomonnom): Async methods
#[macro_export]
macro_rules! enum_dispatch {
// This arm is used to avoid nested loops with the arguments
// The arguments are transformed to $combined_args tt
(@match [$($variant:ident),+]: $fnc:ident, $self:ident, $combined_args:tt) => {
match $self {
$(
Self::$variant(inner) => inner.$fnc$combined_args,
)+
}
};

// Create the function and extract self fron the $args tt (little hack)
(@fnc [$($variant:ident),+]: $vis:vis fn $fnc:ident($self:ident: $sty:ty $(, $arg:ident: $t:ty)*) -> $ret:ty) => {
#[inline]
$vis fn $fnc($self: $sty, $($arg: $t),*) -> $ret {
$crate::enum_dispatch!(@match [$($variant),+]: $fnc, $self, ($($arg,)*))
}
};

($variants:tt; $($vis:vis fn $fnc:ident$args:tt -> $ret:ty;)+) => {
$(
$crate::enum_dispatch!(@fnc $variants: $vis fn $fnc$args -> $ret);
)+
};
}
Comment thread
1egoman marked this conversation as resolved.
111 changes: 111 additions & 0 deletions livekit-common/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// 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.

//! Foundational types shared across LiveKit crates: participant identities, the
//! encryption enum, and client-protocol constants.

use std::fmt::Display;

use livekit_protocol as proto;

mod enum_dispatch;

// -------------------------------------------------------------------------------------------------
// Client protocol
// -------------------------------------------------------------------------------------------------

/// Legacy client.
pub const CLIENT_PROTOCOL_DEFAULT: i32 = 0;

/// RPC v2 (see RPC spec).
pub const CLIENT_PROTOCOL_DATA_STREAM_RPC: i32 = 1;

// -------------------------------------------------------------------------------------------------
// ParticipantIdentity
Comment thread
1egoman marked this conversation as resolved.
// -------------------------------------------------------------------------------------------------

#[derive(Clone, Default, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct ParticipantIdentity(pub String);

impl From<String> for ParticipantIdentity {
fn from(value: String) -> Self {
Self(value)
}
}

impl From<&str> for ParticipantIdentity {
fn from(value: &str) -> Self {
Self(value.to_string())
}
}

impl From<ParticipantIdentity> for String {
fn from(value: ParticipantIdentity) -> Self {
value.0
}
}

impl Display for ParticipantIdentity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}

impl ParticipantIdentity {
pub fn as_str(&self) -> &str {
&self.0
}
}

// -------------------------------------------------------------------------------------------------
// EncryptionType
// -------------------------------------------------------------------------------------------------

#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum EncryptionType {
#[default]
None,
Gcm,
Custom,
}

impl From<proto::encryption::Type> for EncryptionType {
fn from(value: proto::encryption::Type) -> Self {
match value {
proto::encryption::Type::None => Self::None,
proto::encryption::Type::Gcm => Self::Gcm,
proto::encryption::Type::Custom => Self::Custom,
}
}
}

impl From<EncryptionType> for proto::encryption::Type {
fn from(value: EncryptionType) -> Self {
match value {
EncryptionType::None => Self::None,
EncryptionType::Gcm => Self::Gcm,
EncryptionType::Custom => Self::Custom,
}
}
}

impl From<EncryptionType> for i32 {
fn from(value: EncryptionType) -> Self {
match value {
EncryptionType::None => 0,
EncryptionType::Gcm => 1,
EncryptionType::Custom => 2,
}
}
}
31 changes: 31 additions & 0 deletions livekit-data-stream/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[package]
name = "livekit-data-stream"
description = "Data stream core logic for LiveKit"
version = "0.1.0"
readme = "README.md"
license.workspace = true
edition.workspace = true
repository.workspace = true

[features]
# Exposes test-only hooks used by downstream crates' test suites (e.g.
# `TextStreamReader::new_for_test`). Forwarded from the `livekit` crate's `__lk-e2e-test` feature.
test-utils = []
Comment on lines +9 to +13

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Note to reviewers: should these be separate features or just one?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If we need to keep them, they should be separate (I plan to eventually drop the special feature for E2E testing and instead just skip E2E tests based on env).

However, there's the bigger question of if we should be using non-public methods in integration tests in the first place.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

However, there's the bigger question of if we should be using non-public methods in integration tests in the first place.

Right now, that's how it works so I don't think this makes it worse. IMO I'd rather decouple that conversation and revisit as part of the data streams v2 pull request since I will be making some more extensive updates there.

@1egoman 1egoman Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

If we need to keep them, they should be separate (I plan to eventually drop the special feature for E2E testing and instead just skip E2E tests based on env).

I've combined them into one feature for now, test-utils.


[dependencies]
livekit-common = { workspace = true }
livekit-protocol = { workspace = true }
log = { workspace = true }
thiserror = { workspace = true }
parking_lot = { workspace = true }
bytes = { workspace = true }
tokio = { workspace = true, default-features = false, features = ["sync", "fs", "io-util", "rt"] }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

note: Outside the scope of this PR and a pre-existing issue, but we should only depend on tokio with the sync feature unconditionally.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good to know, I will investigate in the data-streams-v2 follow up pull request.

futures-util = { workspace = true, default-features = false, features = ["sink"] }
prost = "0.12"
chrono = "0.4.38"
flate2 = "1"
bmrng = "0.5.2"
uuid = { version = "1", features = ["v4"] }

[dev-dependencies]
tokio = { workspace = true, default-features = false, features = ["macros", "rt", "rt-multi-thread", "time"] }
Loading
Loading