diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml
index 6fbae309..2e5dbad7 100644
--- a/.github/workflows/builds.yml
+++ b/.github/workflows/builds.yml
@@ -7,7 +7,6 @@ on:
- src/**
- include/**
- cpp-example-collection/**
- - bridge/**
- client-sdk-rust/**
- cmake/**
- scripts/**
@@ -28,7 +27,6 @@ on:
- src/**
- include/**
- cpp-example-collection/**
- - bridge/**
- client-sdk-rust/**
- cmake/**
- scripts/**
@@ -58,7 +56,7 @@ env:
# failing the build.
SCCACHE_GHA_ENABLED: "true"
# Pinned commit for cpp-example-collection smoke build (https://github.com/livekit-examples/cpp-example-collection)
- CPP_EXAMPLE_COLLECTION_REF: 56815733a71c14692569e8adf2916a56a14d4882
+ CPP_EXAMPLE_COLLECTION_REF: 402e6fbcc3cb8b2b2aaf80e21b289f27a9060dc6
# vcpkg binary caching for Windows
VCPKG_DEFAULT_TRIPLET: x64-windows-static-md
VCPKG_DEFAULT_HOST_TRIPLET: x64-windows-static-md
diff --git a/.github/workflows/docker-images.yml b/.github/workflows/docker-images.yml
index 5803e340..1e9aa5a6 100644
--- a/.github/workflows/docker-images.yml
+++ b/.github/workflows/docker-images.yml
@@ -6,7 +6,6 @@ on:
paths:
- src/**
- include/**
- - bridge/**
- client-sdk-rust/**
- CMakeLists.txt
- build.sh
@@ -90,7 +89,7 @@ jobs:
fi
case "${path}" in
- docker/Dockerfile.sdk|src/*|include/*|bridge/*|client-sdk-rust/*|cmake/*|data/*|CMakeLists.txt|build.sh|build.cmd|build.h.in|.build-info.json.in|CMakePresets.json)
+ docker/Dockerfile.sdk|src/*|include/*|client-sdk-rust/*|cmake/*|data/*|CMakeLists.txt|build.sh|build.cmd|build.h.in|.build-info.json.in|CMakePresets.json)
sdk_changed=true
;;
esac
diff --git a/.github/workflows/docker-validate.yml b/.github/workflows/docker-validate.yml
index ffd0d121..63c94817 100644
--- a/.github/workflows/docker-validate.yml
+++ b/.github/workflows/docker-validate.yml
@@ -11,7 +11,7 @@ permissions:
env:
# Pinned commit for cpp-example-collection smoke build (https://github.com/livekit-examples/cpp-example-collection)
- CPP_EXAMPLE_COLLECTION_REF: 56815733a71c14692569e8adf2916a56a14d4882
+ CPP_EXAMPLE_COLLECTION_REF: 402e6fbcc3cb8b2b2aaf80e21b289f27a9060dc6
jobs:
validate-x64:
diff --git a/.github/workflows/license_check.yml b/.github/workflows/license_check.yml
index f28aac16..fbfd9ea1 100644
--- a/.github/workflows/license_check.yml
+++ b/.github/workflows/license_check.yml
@@ -18,7 +18,7 @@ jobs:
set -euo pipefail
search_dirs=()
- for dir in src include bridge cpp-example-collection; do
+ for dir in src include cpp-example-collection; do
if [[ -d "$dir" ]]; then
search_dirs+=("$dir")
fi
diff --git a/AGENTS.md b/AGENTS.md
index 6663fdef..e234ec3a 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -74,7 +74,6 @@ Be sure to update the directory layout in this file if the directory layout chan
| `src/` | Implementation files and internal-only headers (`ffi_client.h`, `lk_log.h`, etc.) |
| `src/tests/` | Google Test integration and stress tests |
| `examples/` | In-tree example applications |
-| `bridge/` | **Deprecated** C-style bridge layer — do not add new functionality |
| `client-sdk-rust/` | Git submodule holding the Rust core of the SDK|
| `client-sdk-rust/livekit-ffi/protocol/*.proto` | FFI contract (protobuf definitions, read-only reference) |
| `cmake/` | Build helpers (`protobuf.cmake`, `spdlog.cmake`, `LiveKitConfig.cmake.in`) |
@@ -368,11 +367,6 @@ When adding new client facing functionality, add benchmarking to understand the
- Declare all data objects at the smallest possible level of scope
- Each calling function must check the return value of nonvoid functions, and each called function must check the validity of all parameters provided by the caller
-
-## Deprecated / Out of Scope
-
-- **`bridge/`** (`livekit_bridge`) is deprecated. Do not add new functionality to it.
-
## Common Pitfalls
- A `Room` with `auto_subscribe = false` will never receive remote audio/video frames — this is almost never what you want.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 895284b1..ebfeeeb9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -14,6 +14,7 @@ project(livekit VERSION ${LIVEKIT_PROJECT_VERSION} LANGUAGES C CXX)
set(LIVEKIT_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR})
set(LIVEKIT_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR})
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
+include(warnings)
option(LIVEKIT_BUILD_EXAMPLES "Build LiveKit examples" OFF)
option(LIVEKIT_BUILD_TESTS "Build LiveKit tests" OFF)
@@ -113,17 +114,28 @@ if(TARGET protobuf::libprotobuf)
else()
message(FATAL_ERROR "No protobuf library target found (expected protobuf::libprotobuf)")
endif()
-target_include_directories(livekit_proto PRIVATE
- "${PROTO_BINARY_DIR}"
- ${Protobuf_INCLUDE_DIRS}
-)
-target_link_libraries(livekit_proto PRIVATE ${LIVEKIT_PROTOBUF_TARGET})
+set(LIVEKIT_PROTOBUF_DEP_INCLUDE_DIRS ${Protobuf_INCLUDE_DIRS})
if(TARGET absl::base)
- get_target_property(_absl_inc absl::base INTERFACE_INCLUDE_DIRECTORIES)
+ livekit_get_interface_includes(absl::base _absl_inc)
if(_absl_inc)
- target_include_directories(livekit_proto PRIVATE ${_absl_inc})
+ list(APPEND LIVEKIT_PROTOBUF_DEP_INCLUDE_DIRS ${_absl_inc})
endif()
endif()
+set(LIVEKIT_SYSTEM_INCLUDE_DIRS)
+if(TARGET spdlog::spdlog)
+ livekit_get_interface_includes(spdlog::spdlog _spdlog_inc)
+ if(_spdlog_inc)
+ list(APPEND LIVEKIT_SYSTEM_INCLUDE_DIRS ${_spdlog_inc})
+ endif()
+endif()
+target_include_directories(livekit_proto SYSTEM PRIVATE
+ "${PROTO_BINARY_DIR}"
+)
+target_include_directories(livekit_proto PRIVATE
+ ${LIVEKIT_PROTOBUF_DEP_INCLUDE_DIRS}
+)
+target_link_libraries(livekit_proto PRIVATE ${LIVEKIT_PROTOBUF_TARGET})
+livekit_disable_warnings(livekit_proto)
# Manually generate protobuf files to avoid path prefix issues
set(PROTO_SRCS)
@@ -432,8 +444,13 @@ target_include_directories(livekit
PRIVATE
${LIVEKIT_ROOT_DIR}/src
${LIVEKIT_ROOT_DIR}/src/trace
- ${RUST_ROOT}/livekit-ffi/include
- ${PROTO_BINARY_DIR}
+ ${LIVEKIT_PROTOBUF_DEP_INCLUDE_DIRS}
+)
+
+target_include_directories(livekit SYSTEM PRIVATE
+ ${RUST_ROOT}/livekit-ffi/include
+ ${PROTO_BINARY_DIR}
+ ${LIVEKIT_SYSTEM_INCLUDE_DIRS}
)
target_link_libraries(livekit
@@ -748,9 +765,6 @@ install(FILES
# ------------------------------------------------------------------------
-# Build the LiveKit C++ bridge before examples (human_robot depends on it)
-add_subdirectory(bridge)
-
if(LIVEKIT_BUILD_EXAMPLES)
include(cpp-example-collection)
livekit_configure_cpp_example_collection()
diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md
index 5de560a3..eb223af9 100644
--- a/DEPENDENCIES.md
+++ b/DEPENDENCIES.md
@@ -254,6 +254,6 @@ To verify you have all dependencies linked correctly:
## Need Help?
-- See `README_BUILD.md` for complete build instructions
+- See [`docs/building.md`](docs/building.md) for complete build instructions
- Check `CMakeLists.txt` lines 287-381 for the exact CMake configuration
- Open an issue at: https://github.com/livekit/client-sdk-cpp/issues
diff --git a/README.md b/README.md
index 665f0042..d9fd9d71 100644
--- a/README.md
+++ b/README.md
@@ -8,624 +8,240 @@
-# C++ SDK for LiveKit
+# C++ client SDK for LiveKit
-Use this SDK to add realtime video, audio and data features to your C++ app. By connecting to LiveKit Cloud or a self-hosted server, you can quickly build applications such as multi-modal AI, live streaming, or video calls with just a few lines of code.
+Use this SDK to add realtime video, audio and data features to your C++ app. By connecting to LiveKit Cloud or a self-hosted server, you can quickly build applications such as multimodal AI, live streaming, or video calls with just a few lines of code.
-## Requirements
-- **CMake** ≥ 3.20
-- **Rust / Cargo** (latest stable toolchain)
-- **Git LFS** (required for examples)
- Some example data files (e.g., audio assets) are stored using Git LFS.
- You must install Git LFS before cloning or pulling the repo if you want to run the examples.
-- **livekit-cli** install livekit-cli by following the [official LiveKit docs](https://docs.livekit.io/intro/basics/cli/start/)
-- **livekit-server** install livekit-server by following the [official LiveKit docs](https://docs.livekit.io/transport/self-hosting/local/)
+[](https://github.com/livekit/client-sdk-cpp/actions/workflows/builds.yml)
+[](https://github.com/livekit/client-sdk-cpp/actions/workflows/tests.yml)
-**Platform-Specific Requirements:**
+## Docs
-### For Building the SDK:
-- **Windows:** Visual Studio 2019+, vcpkg
-- **Linux:** `sudo apt install libprotobuf-dev libssl-dev` (protobuf 3.x)
-- **macOS:** `brew install protobuf` (protobuf 3.x)
+- [LiveKit docs](https://docs.livekit.io)
+- [SDK reference](https://docs.livekit.io/reference/client-sdk-cpp/)
+- [Repository docs](./docs/README.md)
-### For Using the Pre-built SDK:
-- **Windows:** All dependencies included (DLLs bundled) - ready to use
-- **Linux:** Requires `libprotobuf` and `libssl-dev`; deploy `liblivekit_ffi.so` with your executable
-- **macOS:** Requires `protobuf`; deploy `liblivekit_ffi.dylib` with your executable
+## Using the SDK
-> **Note**: If the SDK was built with Protobuf 6.0+, you also need `libabsl-dev` (Linux) or `abseil` (macOS).
+[CMake](https://cmake.org/) (≥ 3.20) is used for building the SDK itself and for consuming it as a library. The
+[**cpp-example-collection**](https://github.com/livekit-examples/cpp-example-collection) contains a reference [LiveKitSDK.cmake](https://github.com/livekit-examples/cpp-example-collection/blob/main/cmake/LiveKitSDK.cmake)
+which downloads the latest stable release at CMake configure time. See [docs/building.md](docs/building.md) for additional documentation on integrating LiveKit into your project.
-## Clone the Repository
-
-Make sure to initialize the Rust submodule (`client-sdk-rust`):
+To build the SDK from source:
```bash
-# Option 1: Clone with submodules in one step
git clone --recurse-submodules https://github.com/livekit/client-sdk-cpp.git
-
-# Option 2: Clone first, then initialize submodules
-git clone https://github.com/livekit/client-sdk-cpp.git
cd client-sdk-cpp
-git submodule update --init --recursive
-
-# Note: If running tests, pull Git LFS to bring in test data:
-git lfs pull
-```
-
-## Building
-
-### Quick Build (Using Build Scripts)
-
-**Linux/macOS:**
-```bash
-./build.sh clean # Clean CMake build artifacts + local-install
-./build.sh clean-all # Deep clean (C++ + Rust + local-install + generated files)
-./build.sh debug # Build Debug version
-./build.sh release # Build Release version
-./build.sh debug-examples # Build Debug with examples
-./build.sh release-examples # Build Release with examples
-./build.sh debug-tests # Build Debug with tests
-./build.sh debug-all # Build Debug with tests + examples
-./build.sh release-tests # Build Release with tests
-./build.sh release-all # Build Release with tests + examples
-```
-**Windows**
-Using build scripts:
-```powershell
-.\build.cmd clean # Clean CMake build artifacts + local-install
-.\build.cmd clean-all # Deep clean (C++ + Rust + local-install + generated files)
-.\build.cmd debug # Build Debug version
-.\build.cmd release # Build Release version
-.\build.cmd debug-examples # Build Debug with examples
-.\build.cmd release-examples # Build Release with examples
-.\build.cmd debug-tests # Build Debug with tests
-.\build.cmd debug-all # Build Debug with tests + examples
-.\build.cmd release-tests # Build Release with tests
-.\build.cmd release-all # Build Release with tests + examples
-```
-
-The build scripts pass an explicit job count to `cmake --build --parallel`. Set
-`CMAKE_BUILD_PARALLEL_LEVEL` to override the default detected logical CPU count.
-
-### Windows build using cmake/vcpkg
-```bash
-cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE="$PWD/vcpkg/scripts/buildsystems/vcpkg.cmake" # Generate Makefiles in build folder
-# Build (Release or Debug)
-cmake --build build --config Release
-# or:
-cmake --build build --config Debug
-# Clean CMake build artifacts
-Remove-Item -Recurse -Force build
-```
-Note (Windows), This assumes vcpkg is checked out in the repo root at `.\vcpkg\`.
-You must install protobuf via vcpkg (so CMake can find ProtobufConfig.cmake and protoc), for example:
-```bash
-.\vcpkg\vcpkg install protobuf:x64-windows
-```
-
-### Advanced Build (Using CMake Presets)
-
-For more control and platform-specific builds, see the detailed instructions in [README_BUILD.md](README_BUILD.md).
-
-**Prerequisites (Windows only):**
-- Set `VCPKG_ROOT` environment variable pointing to your vcpkg installation
-
-```powershell
-# Windows PowerShell
-$env:VCPKG_ROOT = "C:\path\to\vcpkg"
-```
-
-**Prerequisites (Linux/macOS):**
-- Install system dependencies (see above)
-
-**Quick start:**
-```bash
-# Windows
-cmake --preset windows-release
-cmake --build --preset windows-release
-
-# Linux
-cmake --preset linux-release
-cmake --build --preset linux-release
-
-# macOS
-cmake --preset macos-release
-cmake --build --preset macos-release
-```
-
-**For complete build instructions, troubleshooting, and platform-specific notes, see [README_BUILD.md](README_BUILD.md)**
-
-### Building with Docker
-The Docker setup is split into a reusable base image and an SDK image layered on top of it.
- **NOTE:** this has only been tested on Linux
-```bash
-docker build -t livekit-cpp-sdk-base . -f docker/Dockerfile.base
-docker build --build-arg BASE_IMAGE=livekit-cpp-sdk-base -t livekit-cpp-sdk . -f docker/Dockerfile.sdk
-docker run -it --network host livekit-cpp-sdk:latest bash
-```
-
-__NOTE:__ if you are building your own Dockerfile, you will likely need to set the same `ENV` variables as in `docker/Dockerfile.base`, but to the relevant directories:
-```bash
-export CC=$HOME/gcc-14/bin/gcc
-export CXX=$HOME/gcc-14/bin/g++
-export LD_LIBRARY_PATH=$HOME/gcc-14/lib64:$LD_LIBRARY_PATH
-export PATH=$HOME/.cargo/bin:$PATH
-export PATH=$HOME/cmake-3.31/bin:$PATH
-```
-
-## Run Example
-
-### Prerequisites
-
-Ensure one of the `*-examples` build script options was run prior.
-
-### Generate Tokens
-Before running any participant, create JWT tokens with the proper identity and room name, example
-```bash
-lk token create -r test -i your_own_identity --join --valid-for 99999h --dev --room=your_own_room
-```
-
-### SimpleRoom
-
-```bash
-./build-release/cpp-example-collection-build/simple_room/SimpleRoom --url $URL --token
-```
-
-You can also provide the URL and token via environment variables:
-```bash
-export LIVEKIT_URL=ws://localhost:7880
-export LIVEKIT_TOKEN=
-./build-release/cpp-example-collection-build/simple_room/SimpleRoom
-```
-
-**End-to-End Encryption (E2EE)**
-You can enable E2E encryption for the streams via --enable_e2ee and --e2ee_key flags,
-by running the following cmds in two terminals or computers. **Note, jwt_token needs to be different identity**
-```bash
-./build-release/cpp-example-collection-build/simple_room/SimpleRoom --url $URL --token --enable_e2ee --e2ee_key="your_key"
-```
-**Note**, **all participants must use the exact same E2EE configuration and shared key.**
-If the E2EE keys do not match between participants:
-- Media cannot be decrypted
-- Video tracks will appear as a black screen
-- Audio will be silent
-- No explicit error may be shown at the UI level
-
-Press Ctrl-C to exit the example.
-
-### SimpleRpc
-The SimpleRpc example demonstrates how to:
-- Connect multiple participants to the same LiveKit room
-- Register RPC handlers (e.g., arrival, square-root, divide, long-calculation)
-- Send RPC requests from one participant to another
-- Handle success, application errors, unsupported methods, and timeouts
-- Observe round-trip times (RTT) for each RPC call
-
-#### Generate Tokens
-Before running any participant, create JWT tokens with **caller**, **greeter** and **math-genius** identities and room name.
-```bash
-lk token create -r test -i caller --join --valid-for 99999h --dev --room=your_own_room
-lk token create -r test -i greeter --join --valid-for 99999h --dev --room=your_own_room
-lk token create -r test -i math-genius --join --valid-for 99999h --dev --room=your_own_room
-```
-
-#### Start Participants
-Every participant is run as a separate terminal process, note --role needs to match the token identity.
-```bash
-./build-release/cpp-example-collection-build/simple_rpc/SimpleRpc --url $URL --token --role=math-genius
-```
-The caller will automatically:
-- Wait for the greeter and math-genius to join
-- Perform RPC calls
-- Print round-trip times
-- Annotate expected successes or expected failures
-
-### SimpleDataStream
-- The SimpleDataStream example demonstrates how to:
-- Connect multiple participants to the same LiveKit room
-- Register text stream and byte stream handlers by topic (e.g. "chat", "files")
-- Send a text stream (chat message) from one participant to another
-- Send a byte stream (file/image) from one participant to another
-- Attach custom stream metadata (e.g. sent_ms) via stream attributes
-- Measure and print one-way latency on the receiver using sender timestamps
-- Receive streamed chunks and reconstruct the full payload on the receiver
-
-#### Generate Tokens
-Before running any participant, create JWT tokens with caller and greeter identities and your room name.
-```bash
-lk token create -r test -i caller --join --valid-for 99999h --dev --room=your_own_room
-lk token create -r test -i greeter --join --valid-for 99999h --dev --room=your_own_room
-```
-
-#### Start Participants
-Start the receiver first (so it registers stream handlers before messages arrive):
-```bash
-./build-release/cpp-example-collection-build/simple_data_stream/SimpleDataStream --url $URL --token
-```
-On another terminal or computer, start the sender
-```bash
-./build-release/cpp-example-collection-build/simple_data_stream/SimpleDataStream --url $URL --token
+./build.sh release # or .\build.cmd release on Windows
```
-**Sender** (e.g. greeter)
-- Waits for the peer, then sends a text stream ("chat") and a file stream ("files") with timestamps and metadata, logging stream IDs and send times.
-
-**Receiver** (e.g. caller)
-- Registers handlers for text and file streams, logs stream events, computes one-way latency, and saves the received file locally.
-
-
-## Logging
-
-The SDK uses [spdlog](https://github.com/gabime/spdlog) internally but does
-**not** expose it in public headers. All log output goes through a thin public
-API in ``.
-
-### Two-tier filtering
-
-| Tier | When | How | Cost |
-|------|------|-----|------|
-| **Compile-time** | CMake configure | `-DLIVEKIT_LOG_LEVEL=WARN` | Zero -- calls below the level are stripped from the binary |
-| **Runtime** | Any time after `initialize()` | `livekit::setLogLevel(LogLevel::Warn)` | Minimal -- a level check before formatting |
-
-#### Compile-time level (`LIVEKIT_LOG_LEVEL`)
-
-Set once when you configure CMake. Calls below this threshold are completely
-removed by the preprocessor -- no format-string evaluation, no function call.
-
-```bash
-# Development (default): keep everything available
-cmake -DLIVEKIT_LOG_LEVEL=TRACE ..
-
-# Release: strip TRACE / DEBUG / INFO
-cmake -DLIVEKIT_LOG_LEVEL=WARN ..
+Building requires a stable Rust toolchain and platform-specific build
+deps (`protobuf`, `abseil`, `openssl` on Linux). See [docs/building.md](docs/building.md)
+for full prerequisites table, Docker recipe, CMake presets, and troubleshooting.
-# Production: only ERROR and CRITICAL survive
-cmake -DLIVEKIT_LOG_LEVEL=ERROR ..
-```
+### Hello, LiveKit
-Valid values: `TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR`, `CRITICAL`, `OFF`.
+Here is a minimal example of the `main` function for sending and receiving video and data track frames. The [sender](https://github.com/livekit-examples/cpp-example-collection/blob/main/hello_livekit/sender/main.cpp) plays the role of a robot or camera, publishing video and data track frames every 100 ms; the [receiver](https://github.com/livekit-examples/cpp-example-collection/blob/main/hello_livekit/receiver/main.cpp) stands in for the cloud service or operator UI, logging every frame it sees. In a production system the synthetic video would be a robot's perception output and the data track would carry sensor readings or operator commands, but the connection and publishing pattern is the same. Full source for both processes lives in the [cpp-example-collection](https://github.com/livekit-examples/cpp-example-collection/tree/main/hello_livekit) repo.
-#### Runtime level (`setLogLevel`)
+The sender creates tracks and publishes data.
-Among the levels that survived compilation you can still filter at runtime
-without rebuilding:
+**Initialize LiveKit and connect to the room**
```cpp
-#include
+#include "livekit/livekit.h"
-livekit::initialize(); // default level: Info
-livekit::setLogLevel(livekit::LogLevel::Debug); // show more detail
-livekit::setLogLevel(livekit::LogLevel::Warn); // suppress info chatter
-```
+// Get your url and token from env vars, args, etc.
+const std::string url = "wss://hello.livekit.cloud";
+const std::string token = "sender_token";
-### Custom log callback
+// Start the LiveKit SDK before creating rooms or tracks.
+livekit::initialize(livekit::LogLevel::Info);
-Replace the default stderr sink with your own handler. This is the integration
-point for frameworks like ROS2 (`RCLCPP_*` macros), Android logcat, or any
-structured-logging pipeline:
+// Set your room options, here we will use defaults.
+livekit::RoomOptions options;
-```cpp
-#include
-
-livekit::initialize();
-livekit::setLogLevel(livekit::LogLevel::Trace);
-
-livekit::setLogCallback(
- [](livekit::LogLevel level,
- const std::string &logger_name,
- const std::string &message) {
- // Route to your framework, e.g.:
- // RCLCPP_INFO(get_logger(), "[%s] %s", logger_name.c_str(), message.c_str());
- myLogger.log(level, logger_name, message);
- });
-
-// Pass nullptr to restore the default stderr sink:
-livekit::setLogCallback(nullptr);
+// Create the room & connect to the room using a server URL and participant token.
+auto room = std::make_unique();
+if (!room->connect(url, token, options)) {
+ std::cerr << "Failed to connect to LiveKit\n";
+ return 1;
+}
```
-See [`cpp-example-collection/logging_levels/custom_sinks.cpp`](cpp-example-collection/logging_levels/custom_sinks.cpp)
-for three copy-paste-ready patterns: **file logger**, **JSON structured lines**,
-and a **ROS2 bridge** that maps `LogLevel` to `RCLCPP_*` macros.
-
-### Available log levels
-
-| Level | Typical use |
-|-------|-------------|
-| `Trace` | Per-frame / per-packet detail (very noisy) |
-| `Debug` | Diagnostic info useful during development |
-| `Info` | Normal operational messages (connection, track events) |
-| `Warn` | Unexpected but recoverable situations |
-| `Error` | Failures that affect functionality |
-| `Critical` | Unrecoverable errors |
-| `Off` | Suppress all output |
-
----
-
-## Tracing
-
-The SDK includes built-in support for [Chromium tracing](https://www.chromium.org/developers/how-tos/trace-event-profiling-tool/), allowing you to capture detailed performance traces for debugging and optimization.
-
-### Basic Usage
+**Create the VideoSource, which provides frames to the VideoTrack, then create and publish the VideoTrack**
```cpp
-#include
-
-// Start tracing to a file
-livekit::startTracing("trace.json");
-
-// ... run your application ...
+// Get the local participant to create tracks.
+auto participant = room->localParticipant().lock();
+if (!participant)
+{
+ std::cerr << "Unable to get the local participant!" << std::endl;
+ return 1;
+}
-// Stop tracing and flush to file
-livekit::stopTracing();
+// Publish a synthetic camera track named "camera0" backed by a VideoSource.
+auto video_source = std::make_shared(640, 480);
+auto video_track = participant->publishVideoTrack("camera0", video_source, livekit::TrackSource::SOURCE_CAMERA);
+if (!video_track) {
+ std::cerr << "Failed to publish video track\n";
+ return 1;
+}
```
-### Filtering by Category
-
-You can optionally filter which categories to trace:
+**Create and publish the DataTrack**
```cpp
-// Trace only specific categories (supports wildcards)
-livekit::startTracing("trace.json", {"livekit.*", "webrtc.*"});
-```
-
-### Viewing Traces
-
-Open the generated trace file in one of these viewers:
-
-1. **Chrome**: Navigate to `chrome://tracing` and click "Load" to open the trace file
-2. **Perfetto**: Go to https://ui.perfetto.dev and drag-drop your trace file
-
----
-
-## Integration & Stress Tests
-
-The SDK includes integration and stress tests using Google Test (gtest).
-
-### Build Tests
-
-**Linux/macOS:**
-```bash
-./build.sh debug-tests # Build Debug with tests
-./build.sh release-tests # Build Release with tests
-```
-
-**Windows:**
-```powershell
-.\build.cmd debug-tests
-.\build.cmd release-tests
-```
-
-### Run Tests
-
-After building, run tests using ctest or directly:
-
-```bash
-# Run all tests via ctest
-cd build-debug
-ctest --output-on-failure
-
-# Or run test executables directly
-./build-debug/bin/livekit_integration_tests
-./build-debug/bin/livekit_stress_tests
-
-# Run specific test suites
-./build-debug/bin/livekit_integration_tests --gtest_filter="*Rpc*"
-./build-debug/bin/livekit_stress_tests --gtest_filter="*MaxPayloadStress*"
-```
-
-### Test Types
-
-| Executable | Description |
-|------------|-------------|
-| `livekit_integration_tests` | Quick tests (~1-2 minutes) for SDK functionality |
-| `livekit_stress_tests` | Long-running tests (configurable, default 1 hour) |
-
-### Integration & Stress Test Environment Variables
-
-The integration and stress test suites (data tracks, RPC, media multistream,
-etc.) require a LiveKit server and two participant tokens:
-
-```bash
-# Required
-export LIVEKIT_URL="ws://localhost:7880" # or wss://your-server.livekit.cloud
-export LIVEKIT_TOKEN_A=""
-export LIVEKIT_TOKEN_B=""
-
-# Optional (for stress tests)
-export RPC_STRESS_DURATION_SECONDS=3600 # Test duration (default: 1 hour)
-export RPC_STRESS_CALLER_THREADS=4 # Concurrent caller threads (default: 4)
-```
-
-**Generate tokens for the test suites:**
-
-The easiest path is to source the helper script, which will mint both
-participant tokens against a local `livekit-server --dev` and export
-`LIVEKIT_TOKEN_A`, `LIVEKIT_TOKEN_B`, and `LIVEKIT_URL` for the current shell:
-
-```bash
-source .token_helpers/set_data_track_test_tokens.bash
-```
-
-To generate tokens manually instead (e.g. against a non-default server):
+// Publish a data track named "app-data" for app messages.
+auto data_track_result = participant->publishDataTrack("app-data");
+if (!data_track_result) {
+ std::cerr << "Failed to publish data track\n";
+ return 1;
+}
+auto data_track = data_track_result.value();
-```bash
-export LIVEKIT_TOKEN_A="$(lk token create --api-key devkey --api-secret secret -i cpp-test-a \
- --join --valid-for 99999h --room cpp_data_track_test \
- --grant '{"canPublish":true,"canSubscribe":true,"canPublishData":true}' \
- --token-only)"
-export LIVEKIT_TOKEN_B="$(lk token create --api-key devkey --api-secret secret -i cpp-test-b \
- --join --valid-for 99999h --room cpp_data_track_test \
- --grant '{"canPublish":true,"canSubscribe":true,"canPublishData":true}' \
- --token-only)"
+// Release the participant to reduce scope.
+participant.reset();
```
-### Test Coverage
-
-- **SDK Initialization**: Initialize/shutdown lifecycle
-- **Room**: Room creation, options, connection
-- **Audio Frame**: Frame creation, manipulation, edge cases
-- **RPC**: Round-trip calls, max payload (15KB), timeouts, errors, concurrent calls
-- **Stress Tests**: High throughput, bidirectional RPC, memory pressure
-
-## Recommended Setup
-### macOS
-```bash
-brew install cmake protobuf rust
-```
+**Publish video and data track frames every 100ms**
-### Ubuntu / Debian
-```bash
-sudo apt update
-sudo apt install -y cmake protobuf-compiler build-essential
-curl https://sh.rustup.rs -sSf | sh
-```
+```cpp
+int count = 0;
-## Development Tips
-### Update Rust version
-```bash
-cd client-sdk-cpp
-git fetch origin
-git switch -c try-rust-main origin/main
+while (true)
+{
+ // Create a 640x480 RGBA video frame.
+ auto vf = livekit::VideoFrame::create(640, 480, livekit::VideoBufferType::RGBA);
-# Sync submodule URLs and check out what origin/main pins (recursively):
-git submodule sync --recursive
-git submodule update --init --recursive --checkout
+ // Capture the frame. This publishes the frame on VideoTrack camera0.
+ video_source->captureFrame(vf);
-# Now, in case the nested submodule under yuv-sys didn’t materialize, force it explicitly:
-cd ..
-git -C client-sdk-rust/yuv-sys submodule sync --recursive
-git -C client-sdk-rust/yuv-sys submodule update --init --recursive --checkout
+ const std::string message = "hello #" + std::to_string(count);
+ const auto now_microsec =
+ std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count();
-# Sanity check:
-git submodule status --recursive
-```
+ // Create and push stamped DataTrackFrame.
+ const livekit::DataTrackFrame frame{
+ std::vector(message.begin(), message.end()),
+ now_microsec,
+ };
-### If yuv-sys fails to build
-```bash
-cargo clean -p yuv-sys
-cargo build -p yuv-sys -vv
-```
+ // Optionally, capture the result.
+ auto push_result = data_track->tryPush(frame);
+ if (!push_result) {
+ const auto& error = push_result.error();
+ std::cerr << "[warn] Failed to push data frame: code=" << static_cast(error.code)
+ << " message=" << error.message << "\n";
+ }
-### Full clean (Rust + C++ build folders)
-In some cases, you may need to perform a full clean that deletes all build artifacts from both the Rust and C++ folders, plus the local install folder:
-```bash
-./build.sh clean-all
+ ++count;
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+}
```
-## Quality Checks
-
-This SDK leverages various tools and checks to ensure the highest quality of the code.
-
-### Clang Tools
-
-- `clang-tidy`: static analysis checks to catch common C++ pitfalls. See [.clang-tidy](./.clang-tidy) for the list of current checks (enforced in CI on PR)
-- `clang-format`: code formatting and style consistency. See [.clang-format](./.clang-format) for the list of style configurations (enforced in CI on PR)
-
-> **Note**: For Windows, `clang-tidy` is not currently supported for this project because the Visual Studio CMake generator does not produce the compile_commands.json database that `clang-tidy` requires. Similarly, `clang-format` must be installed and run manually on Windows, referencing the root `.clang-format` file.
+The receiver side setup is the same, except now we set callbacks to the relevant tracks.
-To run locally, first install the following:
+**Initialize LiveKit and Connect to the room**
-**macOS:**
-
-```bash
-brew install llvm
-```
-
-This installs `clang-format`, `clang-tidy`, and `run-clang-tidy`. Homebrew may ask you to add `/opt/homebrew/opt/llvm/bin` to your `PATH`.
+```cpp
+#include "livekit/livekit.h"
-**Linux:**
+// Get your url and token from env vars, args, etc.
+const std::string url = "wss://hello.livekit.cloud";
+const std::string token = "receiver_token";
-```bash
-# Ubuntu / Debian:
-sudo apt-get install clang-format clang-tidy clang-tools
-```
+// Start the LiveKit SDK before creating rooms or tracks.
+livekit::initialize(livekit::LogLevel::Info);
-**Pre-commit hook**
+// Set your room options, here we use the defaults.
+livekit::RoomOptions options;
-```bash
-printf '#!/bin/sh\nFILES=$(git diff --cached --name-only --diff-filter=ACMR -- "*.c" "*.cc" "*.cpp" "*.cxx" "*.h" "*.hpp" "*.hxx")\n[ -z "$FILES" ] && exit 0\necho "$FILES" | xargs ./scripts/clang-format.sh --fix\necho "$FILES" | xargs git add\n' > .git/hooks/pre-commit && chmod +x .git/hooks/pre-commit
+// Create the room & connect to the room using a server URL and participant token.
+auto room = std::make_unique();
+if (!room->connect(url, token, options)) {
+ std::cerr << "Failed to connect to LiveKit\n";
+ return 1;
+}
```
-To run `clang-tidy`:
+**Set callbacks for new video and data frames**
-1. Generate `compile_commands.json` and the protobuf headers via a release build:
-
-```bash
-./build.sh release
-```
+```cpp
+// The identity of the participant sending video and data frames.
+const std::string sender_identity = "sender_identity";
-1. Run the clang-tidy wrapper, which uses the same file set, regex filters, and `.clang-tidy` config as CI:
+// Set the callback for new video frames from the sender's "camera0" VideoTrack.
+room->setOnVideoFrameCallback(sender_identity, "camera0", [](const livekit::VideoFrame& frame, std::int64_t) {
+ std::cout << "video frame: " << frame.width() << "x" << frame.height() << "\n";
+});
-```bash
-./scripts/clang-tidy.sh
+// Set the callback for new frames on the sender's "app-data" DataTrack.
+room->addOnDataFrameCallback(sender_identity, "app-data",
+ [](const std::vector& payload, std::optional) {
+ const std::string message(payload.begin(), payload.end());
+ std::cout << "data message: " << message << "\n";
+ });
```
-The wrapper forwards extra arguments to `run-clang-tidy`, examples below:
+For end-to-end samples and a fuller set of demos, see the [cpp-example-collection repo](https://github.com/livekit-examples/cpp-example-collection).
-```bash
-./scripts/clang-tidy.sh -j 4 # Change number of cores used
-./scripts/clang-tidy.sh -checks='-*,misc-const-correctness' # Only run certain checks
-./scripts/clang-tidy.sh -fix # Apply fixes automatically
-```
+## Features
-Output is captured to `clang-tidy.log` at the repo root. This is done as a convenience feature, as often times the terminal buffer is not large enough for all the output.
+- Connect to LiveKit rooms (Cloud or self-hosted)
+- Receive remote audio/video tracks
+- Publish local audio/video tracks
+- Data tracks (low-level) and data streams (high-level)
+- RPC between participants
+- End-to-end encryption (E2EE)
+- Hardware-accelerated codecs (via the underlying Rust SDK)
+- Chromium-style tracing for performance debugging
-To run `clang-format`:
+Supported platforms: **Linux** (x64, arm64), **macOS** (12.3+, Apple Silicon
+& Intel), **Windows** (x64).
-```bash
-./scripts/clang-format.sh
-```
+## Logging & tracing
-With no arguments, runs `clang-format` against every relevant file in the repository against defined `.clang-format` rules.
+The SDK uses a thin public logging API in `` with both
+compile-time and runtime filtering. Plug your own sink in (file, JSON,
+syslog, ROS2 `RCLCPP_*`) via `setLogCallback`. See
+[docs/logging.md](docs/logging.md).
-Pass flags or paths as needed, examples below:
+Chromium-format performance traces can be captured with `startTracing` /
+`stopTracing` and viewed in `chrome://tracing` or
+[ui.perfetto.dev](https://ui.perfetto.dev). See [docs/tracing.md](docs/tracing.md).
-```bash
-./scripts/clang-format.sh --fix # Rewrite files in place
-./scripts/clang-format.sh src/room.cpp include/livekit/room.h # Check just these files
-./scripts/clang-format.sh --fix src/room.cpp # Fix just this file
-```
+## Testing
-Output is captured to `clang-format.log` at the repo root.
+Integration and stress test suites live under `src/tests/`. Build them with
+`./build.sh debug-tests`, point `LIVEKIT_URL` + two participant tokens at a
+local `livekit-server --dev`, and run via `ctest` or directly. See
+[docs/testing.md](docs/testing.md).
-### Memory Checks
+## Developer tools
-Run valgrind on various examples or tests to check for memory leaks and other issues.
+`clang-tidy`, `clang-format`, `valgrind`, and Doxygen are all wired up via
+scripts under `scripts/`. Set up the pre-commit auto-formatter
+with:
```bash
-valgrind --leak-check=full ./build-debug/bin/livekit_integration_tests
-valgrind --leak-check=full ./build-debug/bin/livekit_stress_tests
+./scripts/install-pre-commit.sh
```
-## Running locally
+See [docs/tools.md](docs/tools.md).
-1. Install the livekit-server
-https://docs.livekit.io/transport/self-hosting/local/
+## Deprecation
-Start the livekit-server with data tracks enabled:
-```bash
-LIVEKIT_CONFIG="enable_data_tracks: true" livekit-server --dev
-```
+Future deprecations and deprecation dates will be listed here for the next major release.
-```bash
-# generate tokens, do for all participants
-lk token create \
- --api-key devkey \
- --api-secret secret \
- -i robot \
- --join \
- --valid-for 99999h \
- --room robo_room \
- --grant '{"canPublish":true,"canSubscribe":true,"canPublishData":true}'
-```
+### `v1.0.0`
-## Deprecation
+>NOTE: With the official 1.0.0 release we have introduced breaking changes to previous unofficial versions in order
+to align with other LiveKit client SDKs. See the `v1.0.0` release notes for the full list of changes.
-- livekit_bridge (bridge/ folder) is deprecated. Avoid using it. Migrate to the base SDK. This will be removed on 06/01/2026.
-- setOn*FrameCallback with TrackSource is deprecated. Use track name instead. This will be removed on 06/01/2026.
-- All public headers that do not follow `camelBack()` case. This will be removed on 06/01/2026.
+## Contributing
+PRs welcome. Issues: .
diff --git a/README_BUILD.md b/README_BUILD.md
deleted file mode 100644
index 0c061bb2..00000000
--- a/README_BUILD.md
+++ /dev/null
@@ -1,338 +0,0 @@
-# Build Guide
-
-## Prerequisites
-
-### Required Tools
-1. **CMake** (>= 3.20)
-2. **Rust and Cargo** - For building the Rust FFI layer
-3. **C++ Compiler**
- - Windows: Visual Studio 2019 or later
- - Linux: GCC 9+ or Clang 10+
- - macOS: Xcode 12+
-
-### Dependency Management
-
-This project uses different dependency management strategies per platform:
-
-| Platform | Package Manager | Dependencies |
-|----------|-----------------|-------------|
-| Windows | vcpkg (bundled) | protobuf, abseil (DLLs included in distribution) |
-| Linux | apt/dnf | `libprotobuf-dev libabsl-dev libssl-dev` |
-| macOS | Homebrew | `protobuf abseil` |
-
-#### Windows: Install vcpkg
-
-```powershell
-git clone https://github.com/microsoft/vcpkg.git
-cd vcpkg
-.\bootstrap-vcpkg.bat
-$env:VCPKG_ROOT = "$(Get-Location)"
-```
-
-#### Linux: Install Dependencies
-
-```bash
-# Ubuntu/Debian
-sudo apt update && sudo apt install -y \
- build-essential cmake ninja-build pkg-config \
- llvm-dev libclang-dev clang \
- libprotobuf-dev protobuf-compiler libabsl-dev \
- libssl-dev libva-dev libdrm-dev libgbm-dev libx11-dev libgl1-mesa-dev
-```
-
-#### macOS: Install Dependencies
-
-```bash
-brew install cmake ninja protobuf abseil
-```
-
-## Quick Start
-
-### Method 1: Using Build Scripts (Recommended)
-
-The project provides `build.cmd` (Windows) and `build.sh` (Linux/macOS) scripts for simplified building.
-
-**Windows:**
-```powershell
-# Set vcpkg root (required for Windows)
-$env:VCPKG_ROOT = "C:\path\to\vcpkg"
-
-# Build Release version
-.\build.cmd release
-
-# Build Release with examples
-.\build.cmd release-examples
-
-# Build Debug version
-.\build.cmd debug
-
-# Build Debug with examples
-.\build.cmd debug-examples
-
-# Clean build artifacts
-.\build.cmd clean
-
-# Full clean (C++ + Rust + generated files)
-.\build.cmd clean-all
-```
-
-**Linux:**
-```bash
-# Install system dependencies first (see Prerequisites above)
-
-# Build Release version
-./build.sh release
-
-# Build Release with examples
-./build.sh release-examples
-
-# Build Debug version
-./build.sh debug
-
-# Build Debug with examples
-./build.sh debug-examples
-
-# Clean build artifacts
-./build.sh clean
-
-# Full clean
-./build.sh clean-all
-```
-
-**macOS:**
-```bash
-# Install Homebrew dependencies first (see Prerequisites above)
-
-# Build Release version
-./build.sh release
-
-# Build Release with examples
-./build.sh release-examples
-
-# Build Debug version
-./build.sh debug
-
-# Build Debug with examples
-./build.sh debug-examples
-```
-
-#### Important Notes for Linux
-
-Before building on Linux (especially Ubuntu/WSL), you may need to set environment variables to avoid common build errors.
-
-**Set Build Environment Variables:**
-```bash
-# Suppress deprecated warnings from WebRTC (required for newer GCC versions)
-export CXXFLAGS="-Wno-deprecated-declarations"
-export CFLAGS="-Wno-deprecated-declarations"
-
-# Required for Rust bindgen to find libclang
-export LIBCLANG_PATH=/usr/lib/llvm-14/lib # Adjust version as needed
-```
-
-**Common Issues:**
-
-1. **Missing proto files or client-sdk-rust directory**
- - Solution: Initialize git submodules:
- ```bash
- git submodule update --init --recursive
- ```
-
-2. **Deprecated declaration errors during compilation**
- - Cause: Newer GCC versions (12/13/14) are stricter with WebRTC legacy code
- - Solution: Set `CXXFLAGS` and `CFLAGS` as shown above
-
-3. **Rust bindgen fails with "unable to find libclang"**
- - Cause: Rust bindgen cannot locate libclang library
- - Solution: Set `LIBCLANG_PATH` environment variable pointing to your LLVM installation
-
-### Method 2: Using vcpkg Manifest Mode
-
-vcpkg will automatically install all required dependencies based on `vcpkg.json`.
-
-**Windows:**
-```powershell
-# Configure project
-cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake
-
-# Build
-cmake --build build --config Release
-
-# Optional: Build with examples
-cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake -DLIVEKIT_BUILD_EXAMPLES=ON -DVCPKG_MANIFEST_FEATURES=examples
-cmake --build build --config Release
-```
-
-**Linux/macOS:**
-```bash
-# Configure project
-cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake -DCMAKE_BUILD_TYPE=Release
-
-# Build
-cmake --build build
-
-# Optional: Build with examples
-cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake -DCMAKE_BUILD_TYPE=Release -DLIVEKIT_BUILD_EXAMPLES=ON -DVCPKG_MANIFEST_FEATURES=examples
-cmake --build build
-```
-
-### Method 3: Manual Dependency Installation
-
-If you prefer not to use vcpkg, you can install dependencies manually:
-
-#### Required Dependencies
-- **Protobuf** (>= 5.29)
- - Windows: `vcpkg install protobuf:x64-windows`
- - Linux: `sudo apt install libprotobuf-dev protobuf-compiler`
- - macOS: `brew install protobuf`
-
-- **Abseil** (Required for Protobuf >= 6)
- - Windows: `vcpkg install abseil:x64-windows`
- - Linux: `sudo apt install libabsl-dev`
- - macOS: `brew install abseil`
-
-- **OpenSSL** (Linux only)
- - Linux: `sudo apt install libssl-dev`
-
-#### Build Command
-```bash
-cmake -B build -S . -DCMAKE_BUILD_TYPE=Release
-cmake --build build
-```
-
-## Build Output
-
-After a successful build, you will find the following in the build directories:
-
-```
-build-release/ # Release build output
-├── lib/
-│ ├── livekit.lib / liblivekit.a # Main SDK static library
-│ ├── livekit_ffi.dll / .so / .dylib # Rust FFI dynamic library
-│ └── (Windows only: protobuf, abseil DLLs)
-├── include/ # Public headers (auto-synced)
-│ └── livekit/
-└── bin/ # Example executables (with examples enabled)
- ├── SimpleRoom
- ├── SimpleRpc
- ├── SimpleDataStream
- └── liblivekit_ffi.so / .dylib # (Linux/macOS: copied for runtime)
-```
-
-## Integrating into Your Project
-
-### Using CMake
-
-```cmake
-# Method 1: As a subdirectory
-add_subdirectory(path/to/client-sdk-cpp)
-target_link_libraries(your_target PRIVATE livekit)
-
-# Method 2: Using find_package (requires prior installation)
-find_package(livekit REQUIRED)
-target_link_libraries(your_target PRIVATE livekit)
-```
-
-### Manual Linking
-
-1. Add include path: `build/include`
-2. Link static library:
- - Windows: `build/lib/livekit.lib`
- - Linux: `build/lib/liblivekit.a`
- - macOS: `build/lib/liblivekit.a`
-3. Link/Deploy Rust FFI dynamic library:
- - Windows: Link `livekit_ffi.dll.lib`, deploy `livekit_ffi.dll` with your exe
- - Linux: Deploy `liblivekit_ffi.so` in same directory as your executable
- - macOS: Deploy `liblivekit_ffi.dylib` in same directory as your executable
-4. Link platform-specific system libraries
-
-> **Important**: On Linux/macOS, the `.so`/`.dylib` must be in the same directory as your executable (RPATH is set to `$ORIGIN` / `@executable_path`).
-
-**Windows system libraries:**
-```
-ntdll userenv winmm iphlpapi msdmo dmoguids wmcodecdspuuid
-ws2_32 secur32 bcrypt crypt32
-```
-
-**macOS frameworks:**
-```
-CoreAudio AudioToolbox CoreFoundation Security CoreGraphics
-CoreMedia VideoToolbox AVFoundation CoreVideo Foundation
-AppKit QuartzCore OpenGL IOSurface Metal MetalKit ScreenCaptureKit
-```
-
-**Linux libraries:**
-```
-OpenSSL::SSL OpenSSL::Crypto
-```
-
-## CMake Options
-
-| Option | Default | Description |
-|--------|---------|-------------|
-| `LIVEKIT_BUILD_EXAMPLES` | OFF | Build example applications |
-| `LIVEKIT_VERSION` | "0.1.0" | SDK version number |
-| `LIVEKIT_USE_VCPKG` | ON | Use vcpkg for dependency management |
-
-## Troubleshooting
-
-### Q: Rust code recompiles after modifying C++ code?
-A: This has been fixed. Rust code only recompiles when Rust source files change or the Rust library doesn't exist.
-
-### Q: Cannot find Protobuf or other dependencies?
-A: Make sure you're using the correct CMake toolchain file:
-```bash
--DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake
-```
-
-### Q: Linking fails on Linux?
-A: Ensure you have OpenSSL development packages installed:
-```bash
-sudo apt install libssl-dev
-```
-
-### Q: How to clean the build?
-A: Use the CMake-provided clean targets:
-```bash
-# Clean CMake build artifacts
-cmake --build build --target clean
-
-# Clean Rust build artifacts
-cmake --build build --target cargo_clean
-
-# Clean generated protobuf files
-cmake --build build --target clean_generated
-
-# Complete clean (including deletion of build directory)
-cmake --build build --target clean_all
-```
-
-## Example Applications
-
-Example applications are located in the `examples/` directory:
-
-- **SimpleRoom** - Basic room connection and audio/video handling
-- **SimpleRpc** - RPC call examples
-- **SimpleDataStream** - Data stream transmission examples
-
-Please refer to the README in each example directory for more details.
-
-## Platform-Specific Notes
-
-### Windows
-- Recommended: Visual Studio 2019 or later
-- Architecture: x64
-
-### macOS
-- Requires macOS 12.3+ (ScreenCaptureKit support)
-- Requires Xcode Command Line Tools
-
-### Linux
-- Tested on: Ubuntu 20.04+
-- Requires complete development toolchain
-
-## Support
-
-- GitHub Issues: https://github.com/livekit/client-sdk-cpp/issues
-- LiveKit Documentation: https://docs.livekit.io/
-
diff --git a/benchmarks/data_track_throughput/consumer.cpp b/benchmarks/data_track_throughput/consumer.cpp
index d3fde8d2..d8cf32c1 100644
--- a/benchmarks/data_track_throughput/consumer.cpp
+++ b/benchmarks/data_track_throughput/consumer.cpp
@@ -465,7 +465,7 @@ int main(int argc, char* argv[]) {
std::signal(SIGTERM, handleSignal);
#endif
- livekit::initialize(livekit::LogLevel::Info, livekit::LogSink::kConsole);
+ livekit::initialize(livekit::LogLevel::Info);
try {
ThroughputConsumer consumer(options);
@@ -481,7 +481,7 @@ int main(int argc, char* argv[]) {
throw std::runtime_error("Failed to connect to LiveKit room");
}
- auto* local_participant = room.localParticipant();
+ auto local_participant = room.localParticipant().lock();
if (local_participant == nullptr) {
throw std::runtime_error("Local participant unavailable after connect");
}
diff --git a/benchmarks/data_track_throughput/producer.cpp b/benchmarks/data_track_throughput/producer.cpp
index d1b36a5b..1bb55cc3 100644
--- a/benchmarks/data_track_throughput/producer.cpp
+++ b/benchmarks/data_track_throughput/producer.cpp
@@ -200,13 +200,15 @@ std::string waitForConsumerIdentity(Room& room, const std::string& requested_ide
const auto deadline = std::chrono::steady_clock::now() + timeout;
while (g_running.load() && std::chrono::steady_clock::now() < deadline) {
if (!requested_identity.empty()) {
- if (room.remoteParticipant(requested_identity) != nullptr) {
+ if (!room.remoteParticipant(requested_identity).expired()) {
return requested_identity;
}
} else {
const auto participants = room.remoteParticipants();
- if (participants.size() == 1 && participants.front() != nullptr) {
- return participants.front()->identity();
+ if (participants.size() == 1) {
+ if (auto participant = participants.front().lock()) {
+ return participant->identity();
+ }
}
}
@@ -303,7 +305,7 @@ int main(int argc, char* argv[]) {
std::signal(SIGTERM, handleSignal);
#endif
- livekit::initialize(livekit::LogLevel::Info, livekit::LogSink::kConsole);
+ livekit::initialize(livekit::LogLevel::Info);
try {
Room room;
@@ -316,7 +318,7 @@ int main(int argc, char* argv[]) {
throw std::runtime_error("Failed to connect to LiveKit room");
}
- auto* local_participant = room.localParticipant();
+ auto local_participant = room.localParticipant().lock();
if (local_participant == nullptr) {
throw std::runtime_error("Local participant unavailable after connect");
}
diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt
deleted file mode 100644
index 3b5e9f90..00000000
--- a/bridge/CMakeLists.txt
+++ /dev/null
@@ -1,65 +0,0 @@
-cmake_minimum_required(VERSION 3.20)
-
-project(livekit_bridge LANGUAGES CXX)
-
-set(CMAKE_CXX_STANDARD 17)
-set(CMAKE_CXX_STANDARD_REQUIRED ON)
-
-add_library(livekit_bridge SHARED
- src/livekit_bridge.cpp
- src/bridge_audio_track.cpp
- src/bridge_video_track.cpp
- src/rpc_constants.cpp
- src/rpc_controller.cpp
- src/rpc_controller.h
-)
-
-# Workaround for bridge deprecation: handle visibility differently to avoid
-# major LIVEKIT_API changes across its code, and not affecting the SDK
-if(WIN32)
- set_target_properties(livekit_bridge PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON)
-else()
- set_target_properties(livekit_bridge PROPERTIES
- CXX_VISIBILITY_PRESET default
- C_VISIBILITY_PRESET default
- VISIBILITY_INLINES_HIDDEN OFF
- )
- target_compile_options(livekit_bridge INTERFACE
- -fvisibility=default
- -fno-visibility-inlines-hidden
- )
-endif()
-
-target_include_directories(livekit_bridge
- PUBLIC
- $
- $
- PRIVATE
- ${CMAKE_CURRENT_SOURCE_DIR}/src
- ${LIVEKIT_ROOT_DIR}/src
-)
-
-# Link against the main livekit SDK library (which transitively provides
-# include paths for livekit/*.h and links livekit_ffi).
-target_link_libraries(livekit_bridge
- PUBLIC
- livekit
-)
-
-if(MSVC)
- target_compile_options(livekit_bridge PRIVATE /permissive- /Zc:__cplusplus /W4)
-else()
- target_compile_options(livekit_bridge PRIVATE -Wall -Wextra -Wpedantic)
-endif()
-
-# --- Tests ---
-# Bridge tests default to OFF. They are automatically enabled when the parent
-# SDK tests are enabled (LIVEKIT_BUILD_TESTS=ON), e.g. via ./build.sh debug-tests.
-option(LIVEKIT_BRIDGE_BUILD_TESTS "Build bridge unit tests" OFF)
-
-if(LIVEKIT_BRIDGE_BUILD_TESTS OR LIVEKIT_BUILD_TESTS)
- add_subdirectory(tests)
-endif()
-
-# Bridge examples (robot + human) are built from examples/CMakeLists.txt
-# when LIVEKIT_BUILD_EXAMPLES is ON; see examples/bridge_human_robot/.
diff --git a/bridge/README.md b/bridge/README.md
deleted file mode 100644
index a2c2acf8..00000000
--- a/bridge/README.md
+++ /dev/null
@@ -1,268 +0,0 @@
-# LiveKit Bridge
-
-# **WARNING: This library is deprecated, use the base sdk found in src/**
-
-# **WARNING: This folder and functionality will be removed on 06/01/2026**
-
-A simplified, high-level C++ wrapper around the [LiveKit C++ SDK](../README.md). The bridge abstracts away room lifecycle management, track creation, publishing, and subscription boilerplate so that external codebases can interface with LiveKit in just a few lines. It is intended that this library will be used to bridge the LiveKit C++ SDK into other SDKs such as, but not limited to, Foxglove, ROS, and Rerun.
-
-It is intended that this library closely matches the style of the core LiveKit C++ SDK.
-
-# Prerequisites
-Since this is an extention of the LiveKit C++ SDK, go through the LiveKit C++ SDK installation instructions first:
-*__[LiveKit C++ SDK](../README.md)__*
-
-## Usage Overview
-
-```cpp
-#include "livekit_bridge/livekit_bridge.h"
-#include "livekit/audio_frame.h"
-#include "livekit/video_frame.h"
-#include "livekit/track.h"
-
-// 1. Connect
-livekit_bridge::LiveKitBridge bridge;
-livekit::RoomOptions options;
-options.auto_subscribe = true; // automatically subscribe to all remote tracks
-options.dynacast = false;
-bridge.connect("wss://my-server.livekit.cloud", token, options);
-
-// 2. Create outgoing tracks (RAII-managed)
-auto mic = bridge.createAudioTrack("mic", 48000, 2,
- livekit::TrackSource::SOURCE_MICROPHONE); // name, sample_rate, channels, source
-auto cam = bridge.createVideoTrack("cam", 1280, 720,
- livekit::TrackSource::SOURCE_CAMERA); // name, width, height, source
-
-// 3. Push frames to remote participants
-mic->pushFrame(pcm_data, samples_per_channel);
-cam->pushFrame(rgba_data, timestamp_us);
-
-// 4. Receive frames from a remote participant
-bridge.setOnAudioFrameCallback("remote-peer", livekit::TrackSource::SOURCE_MICROPHONE,
- [](const livekit::AudioFrame& frame) {
- // Called on a background reader thread
- });
-
-bridge.setOnVideoFrameCallback("remote-peer", livekit::TrackSource::SOURCE_CAMERA,
- [](const livekit::VideoFrame& frame, int64_t timestamp_us) {
- // Called on a background reader thread
- });
-
-// 5. RPC (Remote Procedure Call)
-bridge.registerRpcMethod("greet",
- [](const livekit::RpcInvocationData& data) -> std::optional {
- return "Hello, " + data.caller_identity + "!";
- });
-
-std::string response = bridge.performRpc("remote-peer", "greet", "");
-
-bridge.unregisterRpcMethod("greet");
-
-// Controller side: send commands to the publisher
-controller_bridge.requestRemoteTrackMute("robot-1", "mic"); // mute audio track "mic"
-controller_bridge.requestRemoteTrackUnmute("robot-1", "mic"); // unmute it
-
-// 7. Cleanup is automatic (RAII), or explicit:
-mic.reset(); // unpublishes the audio track
-cam.reset(); // unpublishes the video track
-bridge.disconnect();
-```
-
-## Building
-
-The bridge is a component of the `client-sdk-cpp` build. See the "⚙️ BUILD" section of the [LiveKit C++ SDK README](../README.md) for instructions on how to build the bridge.
-
-This produces `liblivekit_bridge` (shared library) and optional `robot_stub`, `human_stub`, `robot`, and `human` executables.
-
-### Using the bridge in your own CMake project
-TODO(sderosa): add instructions on how to use the bridge in your own CMake project.
-
-## Architecture
-
-### Data Flow Overview
-
-```
-Your Application
- | |
- | pushFrame() -----> BridgeAudioTrack | (sending to remote participants)
- | pushFrame() -----> BridgeVideoTrack |
- | |
- | callback() <------ Reader Thread | (receiving from remote participants)
- | |
- +------- LiveKitBridge -----------------+
- |
- LiveKit Room
- |
- LiveKit Server
-```
-
-### Core Components
-
-**`LiveKitBridge`** -- The main entry point. Owns the full room lifecycle: SDK initialization, room connection, track publishing, and frame callback management.
-
-**`BridgeAudioTrack` / `BridgeVideoTrack`** -- RAII handles for published local tracks. Created via `createAudioTrack()` / `createVideoTrack()`. When the `shared_ptr` is dropped, the track is automatically unpublished and all underlying SDK resources are freed. Call `pushFrame()` to send audio/video data to remote participants.
-
-### What is a Reader?
-
-A **reader** is a background thread that receives decoded media frames from a remote participant.
-
-When a remote participant publishes an audio or video track and the room subscribes to it (auto-subscribe is enabled by default), `Room` creates an `AudioStream` or `VideoStream` from that track and spins up a dedicated thread. This thread loops on `stream->read()`, which blocks until a new frame arrives. Each received frame is forwarded to the user's registered callback.
-
-In short:
-
-- **Sending** (you -> remote): `BridgeAudioTrack::pushFrame()` / `BridgeVideoTrack::pushFrame()`
-- **Receiving** (remote -> you): reader threads invoke your registered callbacks
-
-Reader threads are managed by `Room` internally. They are created when a matching remote track is subscribed, and torn down (stream closed, thread joined) when the track is unsubscribed, the callback is unregistered, or the `Room` is destroyed.
-
-### Callback Registration Timing
-
-Callbacks are keyed by `(participant_identity, track_source)`. You can register them **after connecting** but before the remote participant has joined the room. `Room` stores the callback and automatically wires it up when the matching track is subscribed.
-
-> **Note:** Only one callback may be set per `(participant_identity, track_source)` pair. Calling `setOnAudioFrameCallback` or `setOnVideoFrameCallback` again with the same identity and source will silently replace the previous callback. If you need to fan-out a single stream to multiple consumers, do so inside your callback.
-
-This means the typical pattern is:
-
-```cpp
-// Connect first, then register callbacks before the remote participant joins.
-livekit::RoomOptions options;
-options.auto_subscribe = true;
-bridge.connect(url, token, options);
-bridge.setOnAudioFrameCallback("robot-1", livekit::TrackSource::SOURCE_MICROPHONE, my_callback);
-// When robot-1 joins and publishes a mic track, my_callback starts firing.
-```
-
-### Thread Safety
-
-- `LiveKitBridge` uses a mutex to protect the callback map and active reader state.
-- Frame callbacks fire on background reader threads. If your callback accesses shared application state, you are responsible for synchronization.
-- `disconnect()` destroys the `Room`, which closes all streams and joins all reader threads before returning -- it is safe to destroy the bridge immediately after.
-
-## API Reference
-
-### `LiveKitBridge`
-
-| Method | Description |
-|---|---|
-| `connect(url, token, options)` | Connect to a LiveKit room. Initializes the SDK, creates a Room, and connects with auto-subscribe enabled. |
-| `disconnect()` | Disconnect and release all resources. Joins all reader threads. Safe to call multiple times. |
-| `isConnected()` | Returns whether the bridge is currently connected. |
-| `createAudioTrack(name, sample_rate, num_channels, source)` | Create and publish a local audio track with the given `TrackSource` (e.g. `SOURCE_MICROPHONE`, `SOURCE_SCREENSHARE_AUDIO`). Returns an RAII `shared_ptr`. |
-| `createVideoTrack(name, width, height, source)` | Create and publish a local video track with the given `TrackSource` (e.g. `SOURCE_CAMERA`, `SOURCE_SCREENSHARE`). Returns an RAII `shared_ptr`. |
-| `setOnAudioFrameCallback(identity, source, callback)` | Register a callback for audio frames from a specific remote participant + track source. |
-| `setOnVideoFrameCallback(identity, source, callback)` | Register a callback for video frames from a specific remote participant + track source. |
-| `clearOnAudioFrameCallback(identity, source)` | Clear the audio callback for a specific remote participant + track source. Stops and joins the reader thread if active. |
-| `clearOnVideoFrameCallback(identity, source)` | Clear the video callback for a specific remote participant + track source. Stops and joins the reader thread if active. |
-| `performRpc(destination_identity, method, payload, response_timeout?)` | Blocking RPC call to a remote participant. Returns the response payload. Throws `livekit::RpcError` on failure. |
-| `registerRpcMethod(method_name, handler)` | Register a handler for incoming RPC invocations. The handler returns an optional response payload or throws `livekit::RpcError`. |
-| `unregisterRpcMethod(method_name)` | Unregister a previously registered RPC handler. |
-| `requestRemoteTrackMute(identity, track_name)` | Ask a remote participant to mute a track by name. Throws `livekit::RpcError` on failure. |
-| `requestRemoteTrackUnmute(identity, track_name)` | Ask a remote participant to unmute a track by name. Throws `livekit::RpcError` on failure. |
-
-### `BridgeAudioTrack`
-
-| Method | Description |
-|---|---|
-| `pushFrame(data, samples_per_channel, timeout_ms)` | Push interleaved int16 PCM samples. Accepts `std::vector` or raw pointer. |
-| `mute()` / `unmute()` | Mute/unmute the track (stops/resumes sending audio). |
-| `release()` | Explicitly unpublish and free resources. Called automatically by the destructor. |
-| `name()` / `sampleRate()` / `numChannels()` | Accessors for track configuration. |
-
-### `BridgeVideoTrack`
-
-| Method | Description |
-|---|---|
-| `pushFrame(data, timestamp_us)` | Push RGBA pixel data. Accepts `std::vector` or raw pointer + size. |
-| `mute()` / `unmute()` | Mute/unmute the track (stops/resumes sending video). |
-| `release()` | Explicitly unpublish and free resources. Called automatically by the destructor. |
-| `name()` / `width()` / `height()` | Accessors for track configuration. |
-
-## Examples
-- examples/robot.cpp: publishes video and audio from a webcam and microphone. This requires a webcam and microphone to be available.
-- examples/human.cpp: receives and renders video to the screen, receives and plays audio through the speaker.
-
-### Running the examples:
-Note: the following workflow works for both `human` and `robot`.
-
-1. create a `robo_room`
-```
-lk token create \
- --join --room robo_room --identity test_user \
- --valid-for 24h
-```
-
-2. generate tokens for the robot and human
-```
-lk token create --api-key --api-secret \
- --join --room robo_room --identity robot --valid-for 24h
-
-lk token create --api-key --api-secret \
- --join --room robo_room --identity human --valid-for 24h
-```
-
-save these tokens as you will need them to run the examples.
-
-3. kick off the robot:
-```
-export LIVEKIT_URL="wss://your-server.livekit.cloud"
-export LIVEKIT_TOKEN=
-./build-release/bin/robot_stub
-```
-
-4. kick off the human (in a new terminal):
-```
-export LIVEKIT_URL="wss://your-server.livekit.cloud"
-export LIVEKIT_TOKEN=
-./build-release/bin/human
-```
-
-The human will print periodic summaries like:
-
-```
-[human] Audio frame #1: 480 samples/ch, 48000 Hz, 1 ch, duration=0.010s
-[human] Video frame #1: 640x480, 1228800 bytes, ts=0 us
-[human] Status: 500 audio frames, 150 video frames received so far.
-```
-
-## Testing
-
-The bridge includes a unit test suite built with [Google Test](https://github.com/google/googletest). Tests cover
-1. `BridgeAudioTrack`/`BridgeVideoTrack` state management, and
-2. `LiveKitBridge` pre-connection behaviour (callback registration, error handling).
-
-### Building and running tests
-
-Bridge tests are automatically included when you build with the `debug-tests` or `release-tests` command:
-
-```bash
-./build.sh debug-tests
-```
-
-Then run them directly:
-
-```bash
-./build-debug/bin/livekit_bridge_tests
-```
-
-### Standalone bridge tests only
-
-If you want to build bridge tests independently (without the parent SDK tests), set `LIVEKIT_BRIDGE_BUILD_TESTS=ON`:
-
-```bash
-cmake --preset macos-debug -DLIVEKIT_BRIDGE_BUILD_TESTS=ON
-cmake --build build-debug --target livekit_bridge_tests
-```
-
-## Limitations
-
-The bridge is designed for simplicity and currently only supports limited audio and video features. It does not expose:
-
-- We dont support all events defined in the RoomDelegate interface.
-- E2EE configuration
-- data tracks
-- Simulcast tuning
-- Video format selection (RGBA is the default; no format option yet)
-- Custom `RoomOptions` or `TrackPublishOptions`
-- **One callback per (participant, source):** Only a single callback can be registered for each `(participant_identity, track_source)` pair. Re-registering with the same key silently replaces the previous callback. To fan-out a stream to multiple consumers, dispatch from within your single callback.
-
-For advanced use cases, use the full `client-sdk-cpp` API directly, or expand the bridge to support your use case.
diff --git a/bridge/include/livekit_bridge/bridge_audio_track.h b/bridge/include/livekit_bridge/bridge_audio_track.h
deleted file mode 100644
index cdd22342..00000000
--- a/bridge/include/livekit_bridge/bridge_audio_track.h
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright 2025 LiveKit
- *
- * 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.
- */
-
-/// @file bridge_audio_track.h
-/// @brief Handle for a published local audio track.
-
-#pragma once
-
-#include
-#include
-#include
-#include
-#include
-
-namespace livekit {
-class AudioSource;
-class LocalAudioTrack;
-class LocalTrackPublication;
-class LocalParticipant;
-} // namespace livekit
-
-namespace livekit_bridge {
-
-namespace test {
-class BridgeAudioTrackTest;
-} // namespace test
-
-/**
- * Handle to a published local audio track.
- *
- * Created via LiveKitBridge::createAudioTrack(). The bridge retains a
- * reference to every track it creates and will automatically release all
- * tracks when disconnect() is called. To unpublish a track mid-session,
- * call release() explicitly; dropping the shared_ptr alone is not
- * sufficient because the bridge still holds a reference.
- *
- * After release() (whether called explicitly or by the bridge on
- * disconnect), pushFrame() returns false and mute()/unmute() become
- * no-ops. The track object remains valid but inert.
- *
- * All public methods are thread-safe: it is safe to call pushFrame() from
- * one thread while another calls mute()/unmute()/release(), or to call
- * pushFrame() concurrently from multiple threads.
- *
- * Usage:
- * auto mic = bridge.createAudioTrack("mic", 48000, 2,
- * livekit::TrackSource::SOURCE_MICROPHONE);
- * mic->pushFrame(pcm_data, samples_per_channel);
- * mic->mute();
- * mic->release(); // unpublishes the track mid-session
- */
-class BridgeAudioTrack {
-public:
- ~BridgeAudioTrack();
-
- // Non-copyable
- BridgeAudioTrack(const BridgeAudioTrack &) = delete;
- BridgeAudioTrack &operator=(const BridgeAudioTrack &) = delete;
-
- /**
- * Push a PCM audio frame to the track.
- *
- * @param data Interleaved int16 PCM samples.
- * Must contain exactly
- * (samples_per_channel * num_channels) elements.
- * @param samples_per_channel Number of samples per channel in this frame.
- * @param timeout_ms Max time to wait for FFI confirmation.
- * 0 = wait indefinitely (default).
- * @return true if the frame was pushed, false if the track has been released.
- */
- bool pushFrame(const std::vector &data, int samples_per_channel,
- int timeout_ms = 0);
-
- /**
- * Push a PCM audio frame from a raw pointer.
- *
- * @param data Pointer to interleaved int16 PCM samples.
- * @param samples_per_channel Number of samples per channel.
- * @param timeout_ms Max time to wait for FFI confirmation.
- * @return true if the frame was pushed, false if the track has been released.
- */
- bool pushFrame(const std::int16_t *data, int samples_per_channel,
- int timeout_ms = 0);
-
- /// Mute the audio track (stops sending audio to the room).
- void mute();
-
- /// Unmute the audio track (resumes sending audio to the room).
- void unmute();
-
- /// Track name as provided at creation.
- const std::string &name() const noexcept { return name_; }
-
- /// Sample rate in Hz.
- int sampleRate() const noexcept { return sample_rate_; }
-
- /// Number of audio channels.
- int numChannels() const noexcept { return num_channels_; }
-
- /// Whether this track has been released / unpublished.
- bool isReleased() const noexcept;
-
- /**
- * Explicitly unpublish the track and release all underlying SDK resources.
- *
- * After this call, pushFrame() returns false and mute()/unmute() are
- * no-ops. Called automatically by the destructor and by
- * LiveKitBridge::disconnect(). Safe to call multiple times (idempotent).
- */
- void release();
-
-private:
- friend class LiveKitBridge;
- friend class test::BridgeAudioTrackTest;
-
- BridgeAudioTrack(std::string name, int sample_rate, int num_channels,
- std::shared_ptr source,
- std::shared_ptr track,
- std::shared_ptr publication,
- livekit::LocalParticipant *participant);
-
- mutable std::mutex mutex_;
- std::string name_;
- int sample_rate_;
- int num_channels_;
- bool released_ = false;
-
- std::shared_ptr source_;
- std::shared_ptr track_;
- /* DEPRECATED. use track_->publication() instead */
- std::shared_ptr publication_;
- livekit::LocalParticipant *participant_ = nullptr; // not owned
-};
-
-} // namespace livekit_bridge
diff --git a/bridge/include/livekit_bridge/bridge_video_track.h b/bridge/include/livekit_bridge/bridge_video_track.h
deleted file mode 100644
index 27169151..00000000
--- a/bridge/include/livekit_bridge/bridge_video_track.h
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright 2025 LiveKit
- *
- * 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.
- */
-
-/// @file bridge_video_track.h
-/// @brief Handle for a published local video track.
-
-#pragma once
-
-#include
-#include
-#include
-#include
-#include
-
-namespace livekit {
-class VideoSource;
-class LocalVideoTrack;
-class LocalTrackPublication;
-class LocalParticipant;
-} // namespace livekit
-
-namespace livekit_bridge {
-
-namespace test {
-class BridgeVideoTrackTest;
-} // namespace test
-
-/**
- * Handle to a published local video track.
- *
- * Created via LiveKitBridge::createVideoTrack(). The bridge retains a
- * reference to every track it creates and will automatically release all
- * tracks when disconnect() is called. To unpublish a track mid-session,
- * call release() explicitly; dropping the shared_ptr alone is not
- * sufficient because the bridge still holds a reference.
- *
- * After release() (whether called explicitly or by the bridge on
- * disconnect), pushFrame() returns false and mute()/unmute() become
- * no-ops. The track object remains valid but inert.
- *
- * All public methods are thread-safe: it is safe to call pushFrame() from
- * one thread while another calls mute()/unmute()/release(), or to call
- * pushFrame() concurrently from multiple threads.
- *
- * Usage:
- * auto cam = bridge.createVideoTrack("cam", 1280, 720,
- * livekit::TrackSource::SOURCE_CAMERA);
- * cam->pushFrame(rgba_data, timestamp_us);
- * cam->mute();
- * cam->release(); // unpublishes the track mid-session
- */
-class BridgeVideoTrack {
-public:
- ~BridgeVideoTrack();
-
- // Non-copyable
- BridgeVideoTrack(const BridgeVideoTrack &) = delete;
- BridgeVideoTrack &operator=(const BridgeVideoTrack &) = delete;
-
- /**
- * Push an RGBA video frame to the track.
- *
- * @param rgba Raw RGBA pixel data. Must contain exactly
- * (width * height * 4) bytes.
- * @param timestamp_us Presentation timestamp in microseconds.
- * Pass 0 to let the SDK assign one.
- * @return true if the frame was pushed, false if the track has been released.
- */
- bool pushFrame(const std::vector &rgba,
- std::int64_t timestamp_us = 0);
-
- /**
- * Push an RGBA video frame from a raw pointer.
- *
- * @param rgba Pointer to RGBA pixel data.
- * @param rgba_size Size of the data buffer in bytes.
- * @param timestamp_us Presentation timestamp in microseconds.
- * @return true if the frame was pushed, false if the track has been released.
- */
- bool pushFrame(const std::uint8_t *rgba, std::size_t rgba_size,
- std::int64_t timestamp_us = 0);
-
- /// Mute the video track (stops sending video to the room).
- void mute();
-
- /// Unmute the video track (resumes sending video to the room).
- void unmute();
-
- /// Track name as provided at creation.
- const std::string &name() const noexcept { return name_; }
-
- /// Video width in pixels.
- int width() const noexcept { return width_; }
-
- /// Video height in pixels.
- int height() const noexcept { return height_; }
-
- /// Whether this track has been released / unpublished.
- bool isReleased() const noexcept;
-
- /**
- * Explicitly unpublish the track and release all underlying SDK resources.
- *
- * After this call, pushFrame() returns false and mute()/unmute() are
- * no-ops. Called automatically by the destructor and by
- * LiveKitBridge::disconnect(). Safe to call multiple times (idempotent).
- */
- void release();
-
-private:
- friend class LiveKitBridge;
- friend class test::BridgeVideoTrackTest;
-
- BridgeVideoTrack(std::string name, int width, int height,
- std::shared_ptr source,
- std::shared_ptr track,
- std::shared_ptr publication,
- livekit::LocalParticipant *participant);
-
- mutable std::mutex mutex_;
- std::string name_;
- int width_;
- int height_;
- bool released_ = false;
-
- std::shared_ptr source_;
- std::shared_ptr track_;
- /* DEPRECATED. use track_->publication() instead */
- std::shared_ptr publication_;
- livekit::LocalParticipant *participant_ = nullptr; // not owned
-};
-
-} // namespace livekit_bridge
diff --git a/bridge/include/livekit_bridge/livekit_bridge.h b/bridge/include/livekit_bridge/livekit_bridge.h
deleted file mode 100644
index 1f2d44a6..00000000
--- a/bridge/include/livekit_bridge/livekit_bridge.h
+++ /dev/null
@@ -1,374 +0,0 @@
-/*
- * Copyright 2025 LiveKit
- *
- * 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.
- */
-
-/// @file livekit_bridge.h
-/// @brief High-level bridge API for the LiveKit C++ SDK.
-
-#pragma once
-
-#include "livekit_bridge/bridge_audio_track.h"
-#include "livekit_bridge/bridge_video_track.h"
-#include "livekit_bridge/rpc_constants.h"
-
-#include "livekit/local_participant.h"
-#include "livekit/room.h"
-#include "livekit/rpc_error.h"
-
-#include
-#include
-#include
-#include
-#include
-
-namespace livekit {
-class Room;
-class AudioFrame;
-class VideoFrame;
-enum class TrackSource;
-} // namespace livekit
-
-namespace livekit_bridge {
-
-class RpcController;
-
-namespace test {
-class LiveKitBridgeTest;
-} // namespace test
-
-/// Callback type for incoming audio frames.
-/// Called on a background reader thread owned by Room.
-using AudioFrameCallback = livekit::AudioFrameCallback;
-
-/// Callback type for incoming video frames.
-/// Called on a background reader thread owned by Room.
-/// @param frame The decoded video frame (RGBA by default).
-/// @param timestamp_us Presentation timestamp in microseconds.
-using VideoFrameCallback = livekit::VideoFrameCallback;
-
-/**
- * High-level bridge to the LiveKit C++ SDK.
- *
- * Owns the full room lifecycle: initialize SDK, create Room, connect,
- * publish tracks, and manage incoming frame callbacks.
- *
- * Frame callback reader threads are managed by Room internally via
- * Room::setOnAudioFrameCallback / Room::setOnVideoFrameCallback.
- *
- * The bridge retains a shared_ptr to every track it creates. On
- * disconnect(), all tracks are released (unpublished) before the room
- * is torn down, guaranteeing safe teardown order. To unpublish a track
- * mid-session, call release() on the track explicitly; dropping the
- * application's shared_ptr alone is not sufficient.
- *
- * Example:
- *
- * LiveKitBridge bridge;
- * livekit::RoomOptions options;
- * options.auto_subscribe = true;
- * bridge.connect("wss://my-server.livekit.cloud", my_token, options);
- *
- * auto mic = bridge.createAudioTrack("mic", 48000, 2,
- * livekit::TrackSource::SOURCE_MICROPHONE);
- * auto cam = bridge.createVideoTrack("cam", 1280, 720,
- * livekit::TrackSource::SOURCE_CAMERA);
- *
- * mic->pushFrame(pcm_data, samples_per_channel);
- * cam->pushFrame(rgba_data, timestamp_us);
- *
- * bridge.setOnAudioFrameCallback("remote-participant",
- * livekit::TrackSource::SOURCE_MICROPHONE,
- * [](const livekit::AudioFrame& f) { process(f); });
- *
- * bridge.setOnVideoFrameCallback("remote-participant",
- * livekit::TrackSource::SOURCE_CAMERA,
- * [](const livekit::VideoFrame& f, int64_t ts) { render(f); });
- *
- * // Unpublish a single track mid-session:
- * mic->release();
- *
- * // Disconnect releases all remaining tracks and tears down the room:
- * bridge.disconnect();
- */
-class LiveKitBridge {
-public:
- LiveKitBridge();
- ~LiveKitBridge();
-
- // Non-copyable, non-movable (owns room, callbacks)
- LiveKitBridge(const LiveKitBridge &) = delete;
- LiveKitBridge &operator=(const LiveKitBridge &) = delete;
- LiveKitBridge(LiveKitBridge &&) = delete;
- LiveKitBridge &operator=(LiveKitBridge &&) = delete;
-
- // ---------------------------------------------------------------
- // Connection
- // ---------------------------------------------------------------
-
- /**
- * Connect to a LiveKit room.
- *
- * Initializes the SDK (if not already), creates a Room, and performs
- * the WebSocket handshake. This call **blocks** until the connection
- * succeeds or fails. auto_subscribe is enabled so that remote tracks
- * are subscribed automatically.
- *
- * If the bridge is already connected, returns true immediately.
- * If another thread is already in the process of connecting, returns
- * false without blocking.
- *
- * @param url WebSocket URL of the LiveKit server.
- * @param token Access token for authentication.
- * @param options Room options.
- * @return true if connection succeeded (or was already connected).
- */
- bool connect(const std::string &url, const std::string &token,
- const livekit::RoomOptions &options);
-
- /**
- * Disconnect from the room and release all resources.
- *
- * All published tracks are unpublished, reader threads are stopped
- * by Room's destructor, and the SDK is shut down. Safe to call
- * multiple times.
- */
- void disconnect();
-
- /// Whether the bridge is currently connected to a room.
- bool isConnected() const;
-
- // ---------------------------------------------------------------
- // Track creation (publishing)
- // ---------------------------------------------------------------
-
- /**
- * Create and publish a local audio track.
- *
- * The bridge retains a reference to the track internally. To unpublish
- * mid-session, call release() on the returned track. All surviving
- * tracks are automatically released on disconnect().
- *
- * @pre The bridge must be connected (via connect()). Calling this on a
- * disconnected bridge is a programming error.
- *
- * @param name Human-readable track name.
- * @param sample_rate Sample rate in Hz (e.g. 48000).
- * @param num_channels Number of audio channels (1 = mono, 2 = stereo).
- * @param source Track source type (e.g. SOURCE_MICROPHONE). Use a
- * different source (e.g. SOURCE_SCREENSHARE_AUDIO) to
- * publish multiple audio tracks from the same
- * participant that can be independently subscribed to.
- * @return Shared pointer to the published audio track handle (never null).
- * @throws std::runtime_error if the bridge is not connected.
- */
- std::shared_ptr
- createAudioTrack(const std::string &name, int sample_rate, int num_channels,
- livekit::TrackSource source);
-
- /**
- * Create and publish a local video track.
- *
- * The bridge retains a reference to the track internally. To unpublish
- * mid-session, call release() on the returned track. All surviving
- * tracks are automatically released on disconnect().
- *
- * @pre The bridge must be connected (via connect()). Calling this on a
- * disconnected bridge is a programming error.
- *
- * @param name Human-readable track name.
- * @param width Video width in pixels.
- * @param height Video height in pixels.
- * @param source Track source type (default: SOURCE_CAMERA). Use a
- * different source (e.g. SOURCE_SCREENSHARE) to publish
- * multiple video tracks from the same participant that
- * can be independently subscribed to.
- * @return Shared pointer to the published video track handle (never null).
- * @throws std::runtime_error if the bridge is not connected.
- */
- std::shared_ptr
- createVideoTrack(const std::string &name, int width, int height,
- livekit::TrackSource source);
-
- // ---------------------------------------------------------------
- // Incoming frame callbacks (delegates to Room)
- // ---------------------------------------------------------------
-
- /**
- * Set the callback for audio frames from a specific remote participant
- * and track source.
- *
- * Delegates to Room::setOnAudioFrameCallback. The callback fires on a
- * dedicated reader thread owned by Room whenever a new audio frame is
- * received.
- *
- * @note Only **one** callback may be registered per (participant, source)
- * pair. Calling this again with the same identity and source will
- * silently replace the previous callback.
- *
- * @param participant_identity Identity of the remote participant.
- * @param source Track source (e.g. SOURCE_MICROPHONE).
- * @param callback Function to invoke per audio frame.
- */
- void setOnAudioFrameCallback(const std::string &participant_identity,
- livekit::TrackSource source,
- AudioFrameCallback callback);
-
- /**
- * Register a callback for video frames from a specific remote participant
- * and track source.
- *
- * Delegates to Room::setOnVideoFrameCallback.
- *
- * @note Only **one** callback may be registered per (participant, source)
- * pair. Calling this again with the same identity and source will
- * silently replace the previous callback.
- *
- * @param participant_identity Identity of the remote participant.
- * @param source Track source (e.g. SOURCE_CAMERA).
- * @param callback Function to invoke per video frame.
- */
- void setOnVideoFrameCallback(const std::string &participant_identity,
- livekit::TrackSource source,
- VideoFrameCallback callback);
-
- /**
- * Clear the audio frame callback for a specific remote participant + track
- * source.
- *
- * Delegates to Room::clearOnAudioFrameCallback.
- */
- void clearOnAudioFrameCallback(const std::string &participant_identity,
- livekit::TrackSource source);
-
- /**
- * Clear the video frame callback for a specific remote participant + track
- * source.
- *
- * Delegates to Room::clearOnVideoFrameCallback.
- */
- void clearOnVideoFrameCallback(const std::string &participant_identity,
- livekit::TrackSource source);
-
- // ---------------------------------------------------------------
- // RPC (Remote Procedure Call)
- // ---------------------------------------------------------------
-
- /**
- * Initiate a blocking RPC call to a remote participant.
- *
- * Sends a request to the participant identified by
- * @p destination_identity and blocks until a response is received
- * or the call times out.
- *
- * @param destination_identity Identity of the remote participant.
- * @param method Name of the RPC method to invoke.
- * @param payload Request payload string.
- * @param response_timeout Optional timeout in seconds. If not set,
- * the server default (15 s) is used.
- * @return The response payload returned by the remote handler. nullptr if the
- * RPC call fails, or the bridge is not connected.
- */
- std::optional
- performRpc(const std::string &destination_identity, const std::string &method,
- const std::string &payload,
- const std::optional &response_timeout = std::nullopt);
-
- /**
- * Register a handler for incoming RPC method invocations.
- *
- * When a remote participant calls the given @p method_name on this
- * participant, the bridge invokes @p handler. The handler may return
- * an optional response payload or throw a @c livekit::RpcError to
- * signal failure to the caller.
- *
- * If a handler is already registered for @p method_name, it is
- * silently replaced.
- *
- * @param method_name Name of the RPC method to handle.
- * @param handler Callback invoked on each incoming invocation.
- * @return true if the RPC method was registered successfully.
- */
- bool registerRpcMethod(const std::string &method_name,
- livekit::LocalParticipant::RpcHandler handler);
-
- /**
- * Unregister a previously registered RPC method handler.
- *
- * After this call, invocations for @p method_name result in an
- * "unsupported method" error being returned to the remote caller.
- * If no handler is registered for this name, the call is a no-op.
- *
- * @param method_name Name of the RPC method to unregister.
- * @return true if the RPC method was unregistered successfully.
- */
- bool unregisterRpcMethod(const std::string &method_name);
-
- // ---------------------------------------------------------------
- // Remote Track Control (via RPC)
- // ---------------------------------------------------------------
-
- /**
- * Request a remote participant to mute a published track.
- *
- * The remote participant must be a LiveKitBridge instance (which
- * automatically registers the built-in track-control RPC handler).
- *
- * @param destination_identity Identity of the remote participant.
- * @param track_name Name of the track to mute.
- * @return true if the track was muted successfully.
- */
- bool requestRemoteTrackMute(const std::string &destination_identity,
- const std::string &track_name);
-
- /**
- * Request a remote participant to unmute a published track.
- *
- * The remote participant must be a LiveKitBridge instance (which
- * automatically registers the built-in track-control RPC handler).
- *
- * @param destination_identity Identity of the remote participant.
- * @param track_name Name of the track to unmute.
- * @return true if the track was unmuted successfully.
- */
- bool requestRemoteTrackUnmute(const std::string &destination_identity,
- const std::string &track_name);
-
-private:
- friend class test::LiveKitBridgeTest;
-
- /// Execute a track action (mute/unmute) by track name.
- /// Used as the TrackActionFn callback for RpcController.
- /// Throws livekit::RpcError if the track is not found.
- /// @pre Caller does NOT hold mutex_ (acquires it internally).
- void executeTrackAction(const rpc::track_control::Action &action,
- const std::string &track_name);
-
- mutable std::mutex mutex_;
- bool connected_;
- bool connecting_; // guards against concurrent connect() calls
- bool sdk_initialized_;
-
- std::unique_ptr room_;
- std::unique_ptr rpc_controller_;
-
- /// All tracks created by this bridge. The bridge retains a shared_ptr so
- /// it can force-release every track on disconnect() before the room is
- /// destroyed, preventing dangling @c participant_ pointers.
- std::vector> published_audio_tracks_;
- /// @copydoc published_audio_tracks_
- std::vector> published_video_tracks_;
-};
-
-} // namespace livekit_bridge
diff --git a/bridge/include/livekit_bridge/rpc_constants.h b/bridge/include/livekit_bridge/rpc_constants.h
deleted file mode 100644
index 2c08df96..00000000
--- a/bridge/include/livekit_bridge/rpc_constants.h
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2026 LiveKit
- *
- * 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.
- */
-
-/// @file rpc_constants.h
-/// @brief Constants for built-in bridge RPC methods.
-
-#pragma once
-
-#include
-
-#ifdef _WIN32
-#ifdef livekit_bridge_EXPORTS
-#define LIVEKIT_BRIDGE_API __declspec(dllexport)
-#else
-#define LIVEKIT_BRIDGE_API __declspec(dllimport)
-#endif
-#else
-#define LIVEKIT_BRIDGE_API
-#endif
-
-namespace livekit_bridge {
-namespace rpc {
-
-/// Built-in RPC method name used by remote track control.
-/// Allows remote participants to mute or unmute tracks
-/// published by this bridge. Must be called after connect().
-/// Audio/video tracks support mute and unmute. Data tracks
-/// only support mute and unmute.
-namespace track_control {
-
-enum class Action { kActionMute, kActionUnmute };
-
-/// RPC method name registered by the bridge for remote track control.
-LIVEKIT_BRIDGE_API extern const char *const kMethod;
-
-/// Payload action strings.
-LIVEKIT_BRIDGE_API extern const char *const kActionMute;
-LIVEKIT_BRIDGE_API extern const char *const kActionUnmute;
-
-/// Delimiter between action and track name in the payload (e.g. "mute:cam").
-LIVEKIT_BRIDGE_API extern const char kDelimiter;
-
-/// Response payload returned on success.
-LIVEKIT_BRIDGE_API extern const char *const kResponseOk;
-
-/// Build a track-control RPC payload: ":".
-LIVEKIT_BRIDGE_API std::string formatPayload(const char *action,
- const std::string &track_name);
-
-} // namespace track_control
-} // namespace rpc
-} // namespace livekit_bridge
diff --git a/bridge/src/bridge_audio_track.cpp b/bridge/src/bridge_audio_track.cpp
deleted file mode 100644
index 5816cfce..00000000
--- a/bridge/src/bridge_audio_track.cpp
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright 2025 LiveKit
- *
- * 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.
- */
-
-/// @file bridge_audio_track.cpp
-/// @brief Implementation of BridgeAudioTrack.
-
-#include "livekit_bridge/bridge_audio_track.h"
-
-#include "livekit/audio_frame.h"
-#include "livekit/audio_source.h"
-#include "livekit/local_audio_track.h"
-#include "livekit/local_participant.h"
-
-#include
-#include
-
-namespace livekit_bridge {
-
-BridgeAudioTrack::BridgeAudioTrack(
- std::string name, int sample_rate, int num_channels,
- std::shared_ptr source,
- std::shared_ptr track,
- std::shared_ptr publication,
- livekit::LocalParticipant *participant)
- : name_(std::move(name)), sample_rate_(sample_rate),
- num_channels_(num_channels), source_(std::move(source)),
- track_(std::move(track)), publication_(std::move(publication)),
- participant_(participant) {}
-
-BridgeAudioTrack::~BridgeAudioTrack() { release(); }
-
-bool BridgeAudioTrack::pushFrame(const std::vector &data,
- int samples_per_channel, int timeout_ms) {
- livekit::AudioFrame frame(std::vector(data.begin(), data.end()),
- sample_rate_, num_channels_, samples_per_channel);
-
- std::lock_guard lock(mutex_);
- if (released_) {
- return false;
- }
-
- try {
- source_->captureFrame(frame, timeout_ms);
- } catch (const std::exception &e) {
- std::cerr << "[error] BridgeAudioTrack captureFrame error: " << e.what() << "\n";
- return false;
- }
- return true;
-}
-
-bool BridgeAudioTrack::pushFrame(const std::int16_t *data,
- int samples_per_channel, int timeout_ms) {
- const int total_samples = samples_per_channel * num_channels_;
- livekit::AudioFrame frame(
- std::vector(data, data + total_samples), sample_rate_,
- num_channels_, samples_per_channel);
-
- std::lock_guard lock(mutex_);
- if (released_) {
- return false;
- }
-
- try {
- source_->captureFrame(frame, timeout_ms);
- } catch (const std::exception &e) {
- std::cerr << "[error] BridgeAudioTrack captureFrame error: " << e.what() << "\n";
- return false;
- }
- return true;
-}
-
-void BridgeAudioTrack::mute() {
- std::lock_guard lock(mutex_);
- if (!released_ && track_) {
- track_->mute();
- }
-}
-
-void BridgeAudioTrack::unmute() {
- std::lock_guard lock(mutex_);
- if (!released_ && track_) {
- track_->unmute();
- }
-}
-
-bool BridgeAudioTrack::isReleased() const noexcept {
- std::lock_guard lock(mutex_);
- return released_;
-}
-
-void BridgeAudioTrack::release() {
- std::lock_guard lock(mutex_);
- if (released_) {
- return;
- }
- released_ = true;
-
- // Unpublish the track from the room
- if (participant_ && track_ && track_->publication()) {
- try {
- participant_->unpublishTrack(track_->publication()->sid());
- } catch (...) {
- // Best-effort cleanup; ignore errors during teardown
- std::cerr << "[warn] BridgeAudioTrack unpublishTrack error, continuing "
- "with cleanup\n";
- }
- }
-
- // Release SDK objects in reverse order
- track_.reset();
- source_.reset();
- participant_ = nullptr;
-}
-
-} // namespace livekit_bridge
diff --git a/bridge/src/bridge_video_track.cpp b/bridge/src/bridge_video_track.cpp
deleted file mode 100644
index 7a66155f..00000000
--- a/bridge/src/bridge_video_track.cpp
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright 2025 LiveKit
- *
- * 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.
- */
-
-/// @file bridge_video_track.cpp
-/// @brief Implementation of BridgeVideoTrack.
-
-#include "livekit_bridge/bridge_video_track.h"
-
-#include "livekit/local_participant.h"
-#include "livekit/local_video_track.h"
-#include "livekit/video_frame.h"
-#include "livekit/video_source.h"
-
-#include
-#include
-
-namespace livekit_bridge {
-
-BridgeVideoTrack::BridgeVideoTrack(
- std::string name, int width, int height,
- std::shared_ptr source,
- std::shared_ptr track,
- std::shared_ptr publication,
- livekit::LocalParticipant *participant)
- : name_(std::move(name)), width_(width), height_(height),
- source_(std::move(source)), track_(std::move(track)),
- publication_(std::move(publication)), participant_(participant) {}
-
-BridgeVideoTrack::~BridgeVideoTrack() { release(); }
-
-bool BridgeVideoTrack::pushFrame(const std::vector &rgba,
- std::int64_t timestamp_us) {
- livekit::VideoFrame frame(
- width_, height_, livekit::VideoBufferType::RGBA,
- std::vector(rgba.begin(), rgba.end()));
-
- std::lock_guard lock(mutex_);
- if (released_) {
- return false;
- }
-
- try {
- source_->captureFrame(frame, timestamp_us);
- } catch (const std::exception &e) {
- std::cerr << "[error] BridgeVideoTrack captureFrame error: " << e.what() << "\n";
- return false;
- }
- return true;
-}
-
-bool BridgeVideoTrack::pushFrame(const std::uint8_t *rgba,
- std::size_t rgba_size,
- std::int64_t timestamp_us) {
- livekit::VideoFrame frame(width_, height_, livekit::VideoBufferType::RGBA,
- std::vector(rgba, rgba + rgba_size));
-
- std::lock_guard lock(mutex_);
- if (released_) {
- return false;
- }
-
- try {
- source_->captureFrame(frame, timestamp_us);
- } catch (const std::exception &e) {
- std::cerr << "[error] BridgeVideoTrack captureFrame error: " << e.what() << "\n";
- return false;
- }
- return true;
-}
-
-void BridgeVideoTrack::mute() {
- std::lock_guard lock(mutex_);
- if (!released_ && track_) {
- track_->mute();
- }
-}
-
-void BridgeVideoTrack::unmute() {
- std::lock_guard lock(mutex_);
- if (!released_ && track_) {
- track_->unmute();
- }
-}
-
-bool BridgeVideoTrack::isReleased() const noexcept {
- std::lock_guard lock(mutex_);
- return released_;
-}
-
-void BridgeVideoTrack::release() {
- std::lock_guard lock(mutex_);
- if (released_) {
- return;
- }
- released_ = true;
-
- // Unpublish the track from the room
- if (participant_ && track_ && track_->publication()) {
- try {
- participant_->unpublishTrack(track_->publication()->sid());
- } catch (...) {
- // Best-effort cleanup; ignore errors during teardown
- std::cerr << "[warn] BridgeVideoTrack unpublishTrack error, continuing "
- "with cleanup\n";
- }
- }
-
- // Release SDK objects in reverse order
- track_.reset();
- source_.reset();
- participant_ = nullptr;
-}
-
-} // namespace livekit_bridge
diff --git a/bridge/src/livekit_bridge.cpp b/bridge/src/livekit_bridge.cpp
deleted file mode 100644
index b15587ec..00000000
--- a/bridge/src/livekit_bridge.cpp
+++ /dev/null
@@ -1,427 +0,0 @@
-/*
- * Copyright 2025 LiveKit
- *
- * 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.
- */
-
-/// @file livekit_bridge.cpp
-/// @brief Implementation of the LiveKitBridge high-level API.
-
-#include "livekit_bridge/livekit_bridge.h"
-#include "livekit_bridge/rpc_constants.h"
-#include "rpc_controller.h"
-
-#include "livekit/audio_frame.h"
-#include "livekit/audio_source.h"
-#include "livekit/livekit.h"
-#include "livekit/local_audio_track.h"
-#include "livekit/local_participant.h"
-#include "livekit/local_track_publication.h"
-#include "livekit/local_video_track.h"
-#include "livekit/room.h"
-#include "livekit/track.h"
-#include "livekit/video_frame.h"
-#include "livekit/video_source.h"
-
-#include
-#include
-#include
-
-namespace livekit_bridge {
-
-// ---------------------------------------------------------------
-// Construction / Destruction
-// ---------------------------------------------------------------
-
-LiveKitBridge::LiveKitBridge()
- : connected_(false), connecting_(false), sdk_initialized_(false),
- rpc_controller_(std::make_unique(
- [this](const rpc::track_control::Action &action,
- const std::string &track_name) {
- executeTrackAction(action, track_name);
- })) {}
-
-LiveKitBridge::~LiveKitBridge() { disconnect(); }
-
-// ---------------------------------------------------------------
-// Connection
-// ---------------------------------------------------------------
-
-bool LiveKitBridge::connect(const std::string &url, const std::string &token,
- const livekit::RoomOptions &options) {
- // ---- Phase 1: quick check under lock ----
- {
- std::lock_guard lock(mutex_);
-
- if (connected_) {
- return true; // already connected
- }
-
- if (connecting_) {
- return false; // another thread is already connecting
- }
-
- connecting_ = true;
-
- // Initialize the LiveKit SDK (idempotent)
- if (!sdk_initialized_) {
- livekit::initialize();
- sdk_initialized_ = true;
- }
- }
-
- // ---- Phase 2: create room and connect without holding the lock ----
- // This avoids blocking other threads during the network handshake and
- // eliminates the risk of deadlock if the SDK delivers callbacks synchronously
- // during Connect().
- auto room = std::make_unique();
- assert(room != nullptr);
-
- bool result = room->Connect(url, token, options);
- if (!result) {
- std::lock_guard lock(mutex_);
- connecting_ = false;
- return false;
- }
-
- // ---- Phase 3: commit under lock ----
- livekit::LocalParticipant *lp = nullptr;
- {
- std::lock_guard lock(mutex_);
- room_ = std::move(room);
- connected_ = true;
- connecting_ = false;
-
- lp = room_->localParticipant();
- assert(lp != nullptr);
- }
-
- rpc_controller_->enable(lp);
- return true;
-}
-
-void LiveKitBridge::disconnect() {
- // Disable the RPC controller before tearing down the room. This unregisters
- // built-in handlers while the LocalParticipant is still alive.
- if (rpc_controller_ && rpc_controller_->isEnabled()) {
- rpc_controller_->disable();
- }
-
- bool should_shutdown_sdk = false;
-
- {
- std::lock_guard lock(mutex_);
-
- if (!connected_) {
- std::cerr << "[warn] Attempting to disconnect an already disconnected "
- "bridge. Things may not disconnect properly.\n";
- }
-
- connected_ = false;
- connecting_ = false;
-
- for (auto &track : published_audio_tracks_) {
- track->release();
- }
- for (auto &track : published_video_tracks_) {
- track->release();
- }
- published_audio_tracks_.clear();
- published_video_tracks_.clear();
-
- // Room destructor handles stopping all reader threads
- room_.reset();
-
- if (sdk_initialized_) {
- sdk_initialized_ = false;
- should_shutdown_sdk = true;
- }
- }
-
- if (should_shutdown_sdk) {
- livekit::shutdown();
- }
-}
-
-bool LiveKitBridge::isConnected() const {
- std::lock_guard lock(mutex_);
- return connected_;
-}
-
-// ---------------------------------------------------------------
-// Track creation (publishing)
-// ---------------------------------------------------------------
-
-std::shared_ptr
-LiveKitBridge::createAudioTrack(const std::string &name, int sample_rate,
- int num_channels, livekit::TrackSource source) {
- std::lock_guard lock(mutex_);
-
- if (!connected_ || !room_) {
- throw std::runtime_error(
- "createAudioTrack requires an active connection; call connect() first");
- }
-
- // 1. Create audio source (real-time mode, queue_size_ms=0)
- auto audio_source =
- std::make_shared(sample_rate, num_channels, 0);
-
- // 2. Create local audio track
- auto track =
- livekit::LocalAudioTrack::createLocalAudioTrack(name, audio_source);
-
- // 3. Publish with the caller-specified source
- livekit::TrackPublishOptions opts;
- opts.source = source;
-
- auto lp = room_->localParticipant();
- assert(lp != nullptr);
-
- lp->publishTrack(track, opts);
- auto publication = track->publication();
-
- // 4. Wrap in handle and retain a reference
- auto bridge_track = std::shared_ptr(new BridgeAudioTrack(
- name, sample_rate, num_channels, std::move(audio_source),
- std::move(track), publication, lp));
- published_audio_tracks_.emplace_back(bridge_track);
- return bridge_track;
-}
-
-std::shared_ptr
-LiveKitBridge::createVideoTrack(const std::string &name, int width, int height,
- livekit::TrackSource source) {
- std::lock_guard lock(mutex_);
-
- if (!connected_ || !room_) {
- throw std::runtime_error(
- "createVideoTrack requires an active connection; call connect() first");
- }
-
- // 1. Create video source
- auto video_source = std::make_shared(width, height);
-
- // 2. Create local video track
- auto track =
- livekit::LocalVideoTrack::createLocalVideoTrack(name, video_source);
-
- // 3. Publish with the caller-specified source
- livekit::TrackPublishOptions opts;
- opts.source = source;
-
- auto lp = room_->localParticipant();
- assert(lp != nullptr);
-
- lp->publishTrack(track, opts);
- auto publication = track->publication();
-
- // 4. Wrap in handle and retain a reference
- auto bridge_track = std::shared_ptr(
- new BridgeVideoTrack(name, width, height, std::move(video_source),
- std::move(track), publication, lp));
- published_video_tracks_.emplace_back(bridge_track);
- return bridge_track;
-}
-
-// ---------------------------------------------------------------
-// Incoming frame callbacks
-// ---------------------------------------------------------------
-
-void LiveKitBridge::setOnAudioFrameCallback(
- const std::string &participant_identity, livekit::TrackSource source,
- AudioFrameCallback callback) {
- std::lock_guard lock(mutex_);
- if (!room_) {
- std::cerr << "[warn] setOnAudioFrameCallback called before connect(); "
- "ignored\n";
- return;
- }
- room_->setOnAudioFrameCallback(participant_identity, source,
- std::move(callback));
-}
-
-void LiveKitBridge::setOnVideoFrameCallback(
- const std::string &participant_identity, livekit::TrackSource source,
- VideoFrameCallback callback) {
- std::lock_guard lock(mutex_);
- if (!room_) {
- std::cerr << "[warn] setOnVideoFrameCallback called before connect(); "
- "ignored\n";
- return;
- }
- room_->setOnVideoFrameCallback(participant_identity, source,
- std::move(callback));
-}
-
-void LiveKitBridge::clearOnAudioFrameCallback(
- const std::string &participant_identity, livekit::TrackSource source) {
- std::lock_guard lock(mutex_);
- if (!room_) {
- return;
- }
- room_->clearOnAudioFrameCallback(participant_identity, source);
-}
-
-void LiveKitBridge::clearOnVideoFrameCallback(
- const std::string &participant_identity, livekit::TrackSource source) {
- std::lock_guard lock(mutex_);
- if (!room_) {
- return;
- }
- room_->clearOnVideoFrameCallback(participant_identity, source);
-}
-
-// ---------------------------------------------------------------
-// RPC (delegates to RpcController)
-// ---------------------------------------------------------------
-
-std::optional
-LiveKitBridge::performRpc(const std::string &destination_identity,
- const std::string &method, const std::string &payload,
- const std::optional &response_timeout) {
-
- if (!isConnected()) {
- return std::nullopt;
- }
-
- try {
- return rpc_controller_->performRpc(destination_identity, method, payload,
- response_timeout);
- } catch (const std::exception &e) {
- std::cerr << "[LiveKitBridge] Exception: " << e.what() << "\n";
- return std::nullopt;
- } catch (const std::runtime_error &e) {
- std::cerr << "[LiveKitBridge] Runtime error: " << e.what() << "\n";
- return std::nullopt;
- } catch (const livekit::RpcError &e) {
- std::cerr << "[LiveKitBridge] RPC error: " << e.what() << "\n";
- return std::nullopt;
- }
-}
-
-bool LiveKitBridge::registerRpcMethod(
- const std::string &method_name,
- livekit::LocalParticipant::RpcHandler handler) {
-
- if (!isConnected()) {
- return false;
- }
- try {
- rpc_controller_->registerRpcMethod(method_name, std::move(handler));
- return true;
- } catch (const std::exception &e) {
- std::cerr << "[LiveKitBridge] Exception: " << e.what() << "\n";
- return false;
- } catch (const std::runtime_error &e) {
- std::cerr << "[LiveKitBridge] Runtime error: " << e.what() << "\n";
- return false;
- } catch (const livekit::RpcError &e) {
- std::cerr << "[LiveKitBridge] RPC error: " << e.what() << "\n";
- return false;
- }
-}
-
-bool LiveKitBridge::unregisterRpcMethod(const std::string &method_name) {
- if (!isConnected()) {
- return false;
- }
- try {
- rpc_controller_->unregisterRpcMethod(method_name);
- return true;
- } catch (const std::exception &e) {
- std::cerr << "[LiveKitBridge] Exception: " << e.what() << "\n";
- return false;
- } catch (const std::runtime_error &e) {
- std::cerr << "[LiveKitBridge] Runtime error: " << e.what() << "\n";
- return false;
- } catch (const livekit::RpcError &e) {
- std::cerr << "[LiveKitBridge] RPC error: " << e.what() << "\n";
- return false;
- }
-}
-
-bool LiveKitBridge::requestRemoteTrackMute(
- const std::string &destination_identity, const std::string &track_name) {
- if (!isConnected()) {
- return false;
- }
- try {
- rpc_controller_->requestRemoteTrackMute(destination_identity, track_name);
- return true;
- } catch (const std::exception &e) {
- std::cerr << "[LiveKitBridge] Exception: " << e.what() << "\n";
- return false;
- } catch (const std::runtime_error &e) {
- std::cerr << "[LiveKitBridge] Runtime error: " << e.what() << "\n";
- return false;
- } catch (const livekit::RpcError &e) {
- std::cerr << "[LiveKitBridge] RPC error: " << e.what() << "\n";
- return false;
- }
-}
-
-bool LiveKitBridge::requestRemoteTrackUnmute(
- const std::string &destination_identity, const std::string &track_name) {
- if (!isConnected()) {
- return false;
- }
- try {
- rpc_controller_->requestRemoteTrackUnmute(destination_identity, track_name);
- return true;
- } catch (const std::exception &e) {
- std::cerr << "[LiveKitBridge] Exception: " << e.what() << "\n";
- return false;
- } catch (const std::runtime_error &e) {
- std::cerr << "[LiveKitBridge] Runtime error: " << e.what() << "\n";
- return false;
- } catch (const livekit::RpcError &e) {
- std::cerr << "[LiveKitBridge] RPC error: " << e.what() << "\n";
- return false;
- }
-}
-
-// ---------------------------------------------------------------
-// Track action callback for RpcController
-// ---------------------------------------------------------------
-
-void LiveKitBridge::executeTrackAction(const rpc::track_control::Action &action,
- const std::string &track_name) {
- std::lock_guard lock(mutex_);
-
- for (auto &track : published_audio_tracks_) {
- if (track->name() == track_name && !track->isReleased()) {
- if (action == rpc::track_control::Action::kActionMute) {
- track->mute();
- } else {
- track->unmute();
- }
- return;
- }
- }
-
- for (auto &track : published_video_tracks_) {
- if (track->name() == track_name && !track->isReleased()) {
- if (action == rpc::track_control::Action::kActionMute) {
- track->mute();
- } else {
- track->unmute();
- }
- return;
- }
- }
-
- throw livekit::RpcError(livekit::RpcError::ErrorCode::APPLICATION_ERROR,
- "track not found: " + track_name);
-}
-
-} // namespace livekit_bridge
diff --git a/bridge/src/rpc_constants.cpp b/bridge/src/rpc_constants.cpp
deleted file mode 100644
index 03386fe9..00000000
--- a/bridge/src/rpc_constants.cpp
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2026 LiveKit
- *
- * 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.
- */
-
-#include "livekit_bridge/rpc_constants.h"
-
-namespace livekit_bridge {
-namespace rpc {
-namespace track_control {
-
-const char *const kMethod = "lk.bridge.track-control";
-const char *const kActionMute = "mute";
-const char *const kActionUnmute = "unmute";
-const char kDelimiter = ':';
-const char *const kResponseOk = "ok";
-
-std::string formatPayload(const char *action, const std::string &track_name) {
- std::string payload;
- payload.reserve(std::char_traits::length(action) + 1 +
- track_name.size());
- payload += action;
- payload += kDelimiter;
- payload += track_name;
- return payload;
-}
-
-} // namespace track_control
-} // namespace rpc
-} // namespace livekit_bridge
diff --git a/bridge/src/rpc_controller.cpp b/bridge/src/rpc_controller.cpp
deleted file mode 100644
index 31514666..00000000
--- a/bridge/src/rpc_controller.cpp
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright 2026 LiveKit
- *
- * 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.
- */
-
-/// @file rpc_controller.cpp
-/// @brief Implementation of RpcController.
-
-#include "rpc_controller.h"
-#include "livekit_bridge/rpc_constants.h"
-
-#include "livekit/local_participant.h"
-#include "livekit/rpc_error.h"
-
-#include
-#include
-
-namespace livekit_bridge {
-
-RpcController::RpcController(TrackActionFn track_action_fn)
- : track_action_fn_(std::move(track_action_fn)), lp_(nullptr) {}
-
-void RpcController::enable(livekit::LocalParticipant *lp) {
- assert(lp != nullptr);
- lp_ = lp;
- enableBuiltInHandlers();
-}
-
-void RpcController::disable() {
- if (lp_) {
- disableBuiltInHandlers();
- }
- lp_ = nullptr;
-}
-
-// ---------------------------------------------------------------
-// Generic RPC
-// ---------------------------------------------------------------
-
-std::string
-RpcController::performRpc(const std::string &destination_identity,
- const std::string &method, const std::string &payload,
- const std::optional &response_timeout) {
- assert(lp_ != nullptr);
- return lp_->performRpc(destination_identity, method, payload,
- response_timeout);
-}
-
-// ---------------------------------------------------------------
-// User-registered handlers
-// ---------------------------------------------------------------
-
-void RpcController::registerRpcMethod(
- const std::string &method_name,
- livekit::LocalParticipant::RpcHandler handler) {
- assert(lp_ != nullptr);
- lp_->registerRpcMethod(method_name, std::move(handler));
-}
-
-void RpcController::unregisterRpcMethod(const std::string &method_name) {
- assert(lp_ != nullptr);
- lp_->unregisterRpcMethod(method_name);
-}
-
-// ---------------------------------------------------------------
-// Built-in outgoing convenience (track control)
-// ---------------------------------------------------------------
-
-void RpcController::requestRemoteTrackMute(const std::string &destination_identity,
- const std::string &track_name) {
- namespace tc = rpc::track_control;
- performRpc(destination_identity, tc::kMethod,
- tc::formatPayload(tc::kActionMute, track_name), std::nullopt);
-}
-
-void RpcController::requestRemoteTrackUnmute(
- const std::string &destination_identity, const std::string &track_name) {
- namespace tc = rpc::track_control;
- performRpc(destination_identity, tc::kMethod,
- tc::formatPayload(tc::kActionUnmute, track_name), std::nullopt);
-}
-
-// ---------------------------------------------------------------
-// Built-in handler registration
-// ---------------------------------------------------------------
-
-void RpcController::enableBuiltInHandlers() {
- assert(lp_ != nullptr);
- lp_->registerRpcMethod(rpc::track_control::kMethod,
- [this](const livekit::RpcInvocationData &data)
- -> std::optional {
- return handleTrackControlRpc(data);
- });
-}
-
-void RpcController::disableBuiltInHandlers() {
- assert(lp_ != nullptr);
- lp_->unregisterRpcMethod(rpc::track_control::kMethod);
-}
-
-// ---------------------------------------------------------------
-// Built-in handler: track control
-// ---------------------------------------------------------------
-
-std::optional
-RpcController::handleTrackControlRpc(const livekit::RpcInvocationData &data) {
- namespace tc = rpc::track_control;
-
- std::cout << "[RpcController] Handling track control RPC: " << data.payload
- << "\n";
- auto delim = data.payload.find(tc::kDelimiter);
- if (delim == std::string::npos || delim == 0) {
- throw livekit::RpcError(
- livekit::RpcError::ErrorCode::APPLICATION_ERROR,
- "invalid payload format, expected \":\"");
- }
- const std::string action = data.payload.substr(0, delim);
- const std::string track_name = data.payload.substr(delim + 1);
-
- if (action != tc::kActionMute && action != tc::kActionUnmute) {
- throw livekit::RpcError(livekit::RpcError::ErrorCode::APPLICATION_ERROR,
- "unknown action: " + action);
- }
-
- const auto action_enum = action == tc::kActionMute
- ? rpc::track_control::Action::kActionMute
- : rpc::track_control::Action::kActionUnmute;
-
- track_action_fn_(action_enum, track_name);
- return tc::kResponseOk;
-}
-
-} // namespace livekit_bridge
diff --git a/bridge/src/rpc_controller.h b/bridge/src/rpc_controller.h
deleted file mode 100644
index 97d096dd..00000000
--- a/bridge/src/rpc_controller.h
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright 2026 LiveKit
- *
- * 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.
- */
-
-/// @file rpc_controller.h
-/// @brief Internal RPC controller that owns all RPC concerns for the bridge.
-
-#pragma once
-
-#include "livekit/local_participant.h"
-#include "livekit_bridge/rpc_constants.h"
-
-#include
-#include
-#include
-#include
-
-namespace livekit {
-struct RpcInvocationData;
-} // namespace livekit
-
-namespace livekit_bridge {
-
-namespace test {
-class RpcControllerTest;
-} // namespace test
-
-/**
- * Owns all RPC concerns for the LiveKitBridge: built-in handler registration
- * and dispatch, user-registered custom handlers, and outgoing RPC calls.
- *
- * The controller is bound to a LocalParticipant via enable() and unbound via
- * disable(). All public methods require the controller to be enabled (i.e.,
- * enable() has been called and disable() has not).
- *
- * Built-in handlers (e.g., track-control) are automatically registered on
- * enable() and unregistered on disable(). User-registered handlers are
- * forwarded directly to the underlying LocalParticipant.
- *
- * Not part of the public API; lives in bridge/src/.
- */
-class RpcController {
-public:
- /// Callback the bridge provides to execute a track action
- /// (mute/unmute). Throws livekit::RpcError if the track is not found
- /// or the action is invalid.
- using TrackActionFn = std::function;
-
- explicit RpcController(TrackActionFn track_action_fn);
-
- /// Bind to a LocalParticipant and register all built-in RPC handlers.
- /// @pre @p lp must be non-null and remain valid until disable() is called.
- void enable(livekit::LocalParticipant *lp);
-
- /// Unregister built-in handlers and unbind from the LocalParticipant.
- void disable();
-
- /// Whether the controller is currently bound to a LocalParticipant.
- bool isEnabled() const { return lp_ != nullptr; }
-
- // -- Generic RPC --
-
- /// @brief Perform an RPC call to a remote participant.
- /// @param destination_identity Identity of the destination participant.
- /// @param method Name of the RPC method to invoke.
- /// @param payload Request payload to send to the remote
- /// handler.
- /// @param response_timeout Optional timeout in seconds for receiving
- /// a response. If not set, the server default
- /// timeout (15 seconds) is used.
- /// @return The response payload returned by the remote handler.
- /// @throws if the LocalParticipant performRpc fails.
- std::string performRpc(const std::string &destination_identity,
- const std::string &method, const std::string &payload,
- const std::optional &response_timeout);
-
- // -- User-registered handlers --
- /// @brief Register a handler for an incoming RPC method.
- /// @param method_name Name of the RPC method to handle.
- /// @param handler Callback to execute when an invocation is received.
- /// The handler may return an optional response payload
- /// or throw an RpcError to signal failure.
- /// @throws if the LocalParticipant registerRpcMethod fails.
- void registerRpcMethod(const std::string &method_name,
- livekit::LocalParticipant::RpcHandler handler);
-
- /// @brief Unregister a handler for an incoming RPC method.
- /// @param method_name Name of the RPC method to unregister.
- /// @throws if the LocalParticipant unregisterRpcMethod fails.
- void unregisterRpcMethod(const std::string &method_name);
-
- // -- Built-in outgoing convenience (track control) --
-
- /// @brief Request a remote participant to mute a published track.
- /// @param destination_identity Identity of the remote participant.
- /// @param track_name Name of the track to mute.
- /// @throws if the LocalParticipant requestRemoteTrackMute fails.
- void requestRemoteTrackMute(const std::string &destination_identity,
- const std::string &track_name);
- /// @brief Request a remote participant to unmute a published track.
- /// @param destination_identity Identity of the remote participant.
- /// @param track_name Name of the track to unmute.
- /// @throws if the LocalParticipant requestRemoteTrackUnmute fails.
- void requestRemoteTrackUnmute(const std::string &destination_identity,
- const std::string &track_name);
-
-private:
- friend class test::RpcControllerTest;
-
- /// @brief Enable built-in handlers.
- /// @throws if the LocalParticipant registerRpcMethod fails.
- void enableBuiltInHandlers();
-
- /// @brief Disable built-in handlers.
- /// @throws if the LocalParticipant unregisterRpcMethod fails.
- void disableBuiltInHandlers();
-
- /// @brief Handle a track control RPC.
- /// @param data The RPC invocation data.
- /// @return The response payload returned by the remote handler.
- /// @throws if the RPC is invalid or the track is not found.
- std::optional
- handleTrackControlRpc(const livekit::RpcInvocationData &data);
-
- /// Callback to execute a track action RPC
- TrackActionFn track_action_fn_;
-
- /// The LocalParticipant bound to the controller.
- livekit::LocalParticipant *lp_;
-};
-
-} // namespace livekit_bridge
diff --git a/bridge/tests/CMakeLists.txt b/bridge/tests/CMakeLists.txt
deleted file mode 100644
index 227f0a0c..00000000
--- a/bridge/tests/CMakeLists.txt
+++ /dev/null
@@ -1,100 +0,0 @@
-cmake_minimum_required(VERSION 3.20)
-
-# ============================================================================
-# Google Test Setup via FetchContent
-# ============================================================================
-
-include(FetchContent)
-
-FetchContent_Declare(
- googletest
- GIT_REPOSITORY https://github.com/google/googletest.git
- GIT_TAG v1.14.0
-)
-
-# Prevent overriding the parent project's compiler/linker settings on Windows
-set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
-
-# Don't install gtest when installing this project
-set(INSTALL_GTEST OFF CACHE BOOL "" FORCE)
-
-FetchContent_MakeAvailable(googletest)
-
-# Enable CTest
-enable_testing()
-include(GoogleTest)
-
-# ============================================================================
-# Bridge Unit Tests
-# ============================================================================
-
-file(GLOB BRIDGE_TEST_SOURCES
- "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp"
-)
-
-if(BRIDGE_TEST_SOURCES)
- add_executable(livekit_bridge_tests
- ${BRIDGE_TEST_SOURCES}
- )
-
- target_include_directories(livekit_bridge_tests
- PRIVATE
- ${CMAKE_CURRENT_SOURCE_DIR}/../src
- )
-
- target_link_libraries(livekit_bridge_tests
- PRIVATE
- livekit_bridge
- GTest::gtest_main
- )
-
- # Copy shared libraries to test executable directory
- if(WIN32)
- add_custom_command(TARGET livekit_bridge_tests POST_BUILD
- COMMAND ${CMAKE_COMMAND} -E copy_if_different
- $
- $
- COMMAND ${CMAKE_COMMAND} -E copy_if_different
- $
- $
- COMMAND ${CMAKE_COMMAND} -E copy_if_different
- "$/livekit_ffi.dll"
- $
- COMMENT "Copying DLLs to bridge test directory"
- )
- elseif(APPLE)
- add_custom_command(TARGET livekit_bridge_tests POST_BUILD
- COMMAND ${CMAKE_COMMAND} -E copy_if_different
- $
- $
- COMMAND ${CMAKE_COMMAND} -E copy_if_different
- $
- $
- COMMAND ${CMAKE_COMMAND} -E copy_if_different
- "$/liblivekit_ffi.dylib"
- $
- COMMENT "Copying dylibs to bridge test directory"
- )
- else()
- add_custom_command(TARGET livekit_bridge_tests POST_BUILD
- COMMAND ${CMAKE_COMMAND} -E copy_if_different
- $
- $
- COMMAND ${CMAKE_COMMAND} -E copy_if_different
- $
- $
- COMMAND ${CMAKE_COMMAND} -E copy_if_different
- "$/liblivekit_ffi.so"
- $
- COMMENT "Copying shared libraries to bridge test directory"
- )
- endif()
-
- # Register tests with CTest
- gtest_discover_tests(livekit_bridge_tests
- WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
- DISCOVERY_MODE PRE_TEST
- PROPERTIES
- LABELS "bridge_unit"
- )
-endif()
diff --git a/bridge/tests/integration/test_bridge_rpc_roundtrip.cpp b/bridge/tests/integration/test_bridge_rpc_roundtrip.cpp
deleted file mode 100644
index e649450a..00000000
--- a/bridge/tests/integration/test_bridge_rpc_roundtrip.cpp
+++ /dev/null
@@ -1,271 +0,0 @@
-/*
- * Copyright 2026 LiveKit
- *
- * 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.
- */
-
-#include "../common/bridge_test_common.h"
-#include
-
-namespace livekit_bridge {
-namespace test {
-
-class BridgeRpcRoundtripTest : public BridgeTestBase {};
-
-// ---------------------------------------------------------------------------
-// Test 1: Basic RPC round-trip through the bridge.
-//
-// Receiver registers an "echo" handler, caller performs an RPC call, and the
-// response is verified.
-// ---------------------------------------------------------------------------
-TEST_F(BridgeRpcRoundtripTest, BasicRpcRoundTrip) {
- skipIfNotConfigured();
-
- std::cout << "\n=== Bridge RPC Round-Trip Test ===" << std::endl;
-
- LiveKitBridge caller;
- LiveKitBridge receiver;
-
- ASSERT_TRUE(connectPair(caller, receiver));
-
- const std::string receiver_identity = "rpc-receiver";
-
- std::atomic rpc_calls_received{0};
- receiver.registerRpcMethod(
- "echo",
- [&rpc_calls_received](const livekit::RpcInvocationData &data)
- -> std::optional {
- rpc_calls_received++;
- size_t checksum = 0;
- for (char c : data.payload) {
- checksum += static_cast(c);
- }
- return "echo:" + std::to_string(data.payload.size()) + ":" +
- std::to_string(checksum);
- });
-
- std::cout << "RPC handler registered, performing call..." << std::endl;
-
- std::string test_payload = "hello from bridge";
- std::string response =
- caller.performRpc(receiver_identity, "echo", test_payload, 10.0);
-
- size_t expected_checksum = 0;
- for (char c : test_payload) {
- expected_checksum += static_cast(c);
- }
- std::string expected_response =
- "echo:" + std::to_string(test_payload.size()) + ":" +
- std::to_string(expected_checksum);
-
- std::cout << "Response: " << response << std::endl;
- std::cout << "Expected: " << expected_response << std::endl;
-
- EXPECT_EQ(response, expected_response);
- EXPECT_EQ(rpc_calls_received.load(), 1);
-
- receiver.unregisterRpcMethod("echo");
-}
-
-// ---------------------------------------------------------------------------
-// Test 2: RPC error propagation.
-//
-// The handler throws an RpcError with a custom code and message. The caller
-// should catch the same error code, message, and data.
-// ---------------------------------------------------------------------------
-TEST_F(BridgeRpcRoundtripTest, RpcErrorPropagation) {
- skipIfNotConfigured();
-
- std::cout << "\n=== Bridge RPC Error Propagation Test ===" << std::endl;
-
- LiveKitBridge caller;
- LiveKitBridge receiver;
-
- ASSERT_TRUE(connectPair(caller, receiver));
-
- const std::string receiver_identity = "rpc-receiver";
-
- receiver.registerRpcMethod(
- "fail-method",
- [](const livekit::RpcInvocationData &) -> std::optional {
- throw livekit::RpcError(livekit::RpcError::ErrorCode::APPLICATION_ERROR,
- "intentional failure", "extra-data");
- });
-
- std::cout << "Calling method that throws RpcError..." << std::endl;
-
- try {
- caller.performRpc(receiver_identity, "fail-method", "", 10.0);
- FAIL() << "Expected RpcError to be thrown";
- } catch (const livekit::RpcError &e) {
- std::cout << "Caught RpcError: code=" << e.code() << " message=\""
- << e.message() << "\""
- << " data=\"" << e.data() << "\"" << std::endl;
-
- EXPECT_EQ(static_cast(e.code()),
- livekit::RpcError::ErrorCode::APPLICATION_ERROR);
- EXPECT_EQ(e.message(), "intentional failure");
- EXPECT_EQ(e.data(), "extra-data");
- }
-
- receiver.unregisterRpcMethod("fail-method");
-}
-
-// ---------------------------------------------------------------------------
-// Test 3: Calling an unregistered method returns UNSUPPORTED_METHOD.
-// ---------------------------------------------------------------------------
-TEST_F(BridgeRpcRoundtripTest, UnregisteredMethod) {
- skipIfNotConfigured();
-
- std::cout << "\n=== Bridge RPC Unsupported Method Test ===" << std::endl;
-
- LiveKitBridge caller;
- LiveKitBridge receiver;
-
- ASSERT_TRUE(connectPair(caller, receiver));
-
- const std::string receiver_identity = "rpc-receiver";
-
- std::cout << "Calling nonexistent method..." << std::endl;
-
- try {
- caller.performRpc(receiver_identity, "nonexistent-method", "", 5.0);
- FAIL() << "Expected RpcError for unsupported method";
- } catch (const livekit::RpcError &e) {
- std::cout << "Caught RpcError: code=" << e.code() << " message=\""
- << e.message() << "\"" << std::endl;
-
- EXPECT_EQ(static_cast(e.code()),
- livekit::RpcError::ErrorCode::UNSUPPORTED_METHOD);
- }
-}
-
-// ===========================================================================
-// Remote Track Control Tests
-// ===========================================================================
-
-class BridgeRemoteTrackControlTest : public BridgeTestBase {};
-
-// ---------------------------------------------------------------------------
-// Test 4: Remote mute of an audio track.
-//
-// Publisher creates an audio track, enables remote track control. Controller
-// requests mute, then unmute.
-// ---------------------------------------------------------------------------
-TEST_F(BridgeRemoteTrackControlTest, RemoteMuteAudioTrack) {
- skipIfNotConfigured();
-
- std::cout << "\n=== Bridge Remote Mute Audio Track Test ===" << std::endl;
-
- LiveKitBridge publisher;
- LiveKitBridge controller;
-
- ASSERT_TRUE(connectPair(controller, publisher));
-
- const std::string publisher_identity = "rpc-receiver";
-
- auto audio_track = publisher.createAudioTrack(
- "mic", 48000, 1, livekit::TrackSource::SOURCE_MICROPHONE);
- ASSERT_NE(audio_track, nullptr);
-
- std::this_thread::sleep_for(2s);
-
- std::cout << "Requesting mute..." << std::endl;
- EXPECT_NO_THROW(controller.requestRemoteTrackMute(publisher_identity, "mic"));
-
- std::vector silence(480, 0);
- bool pushed_while_muted = audio_track->pushFrame(silence, 480);
- std::cout << "pushFrame while muted: " << pushed_while_muted << std::endl;
-
- std::cout << "Requesting unmute..." << std::endl;
- EXPECT_NO_THROW(
- controller.requestRemoteTrackUnmute(publisher_identity, "mic"));
-
- bool pushed_after_unmute = audio_track->pushFrame(silence, 480);
- EXPECT_TRUE(pushed_after_unmute);
- std::cout << "pushFrame after unmute: " << pushed_after_unmute << std::endl;
-
- audio_track->release();
-}
-
-// ---------------------------------------------------------------------------
-// Test 5: Remote mute of a video track.
-// ---------------------------------------------------------------------------
-TEST_F(BridgeRemoteTrackControlTest, RemoteMuteVideoTrack) {
- skipIfNotConfigured();
-
- std::cout << "\n=== Bridge Remote Mute Video Track Test ===" << std::endl;
-
- LiveKitBridge publisher;
- LiveKitBridge controller;
-
- ASSERT_TRUE(connectPair(controller, publisher));
-
- const std::string publisher_identity = "rpc-receiver";
-
- auto video_track = publisher.createVideoTrack(
- "cam", 320, 240, livekit::TrackSource::SOURCE_CAMERA);
- ASSERT_NE(video_track, nullptr);
-
- std::this_thread::sleep_for(2s);
-
- std::cout << "Requesting mute on video track..." << std::endl;
- EXPECT_NO_THROW(controller.requestRemoteTrackMute(publisher_identity, "cam"));
-
- std::cout << "Requesting unmute on video track..." << std::endl;
- EXPECT_NO_THROW(
- controller.requestRemoteTrackUnmute(publisher_identity, "cam"));
-
- std::vector frame(320 * 240 * 4, 128);
- bool pushed_after_unmute = video_track->pushFrame(frame);
- EXPECT_TRUE(pushed_after_unmute);
- std::cout << "pushFrame after unmute: " << pushed_after_unmute << std::endl;
-
- video_track->release();
-}
-
-// ---------------------------------------------------------------------------
-// Test 7: Remote mute on a nonexistent track returns an error.
-// ---------------------------------------------------------------------------
-TEST_F(BridgeRemoteTrackControlTest, RemoteMuteNonexistentTrack) {
- skipIfNotConfigured();
-
- std::cout << "\n=== Bridge Remote Mute Nonexistent Track Test ==="
- << std::endl;
-
- LiveKitBridge publisher;
- LiveKitBridge controller;
-
- ASSERT_TRUE(connectPair(controller, publisher));
-
- const std::string publisher_identity = "rpc-receiver";
-
- std::this_thread::sleep_for(2s);
-
- std::cout << "Requesting mute on nonexistent track..." << std::endl;
- try {
- controller.requestRemoteTrackMute(publisher_identity, "no-such-track");
- FAIL() << "Expected RpcError for nonexistent track";
- } catch (const livekit::RpcError &e) {
- std::cout << "Caught RpcError: code=" << e.code() << " message=\""
- << e.message() << "\"" << std::endl;
-
- EXPECT_EQ(static_cast(e.code()),
- livekit::RpcError::ErrorCode::APPLICATION_ERROR);
- EXPECT_NE(e.message().find("track not found"), std::string::npos)
- << "Error message should mention 'track not found'";
- }
-}
-
-} // namespace test
-} // namespace livekit_bridge
diff --git a/bridge/tests/test_bridge_audio_track.cpp b/bridge/tests/test_bridge_audio_track.cpp
deleted file mode 100644
index 8e7274e9..00000000
--- a/bridge/tests/test_bridge_audio_track.cpp
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright 2025 LiveKit
- *
- * 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.
- */
-
-/// @file test_bridge_audio_track.cpp
-/// @brief Unit tests for BridgeAudioTrack.
-
-#include
-#include
-
-#include
-#include
-#include
-
-namespace livekit_bridge {
-namespace test {
-
-class BridgeAudioTrackTest : public ::testing::Test {
-protected:
- /// Create a BridgeAudioTrack with null SDK objects for pure-logic testing.
- /// The track is usable for accessor and state management tests but will
- /// crash if pushFrame / mute / unmute try to dereference SDK pointers
- /// on a non-released track.
- static BridgeAudioTrack createNullTrack(const std::string &name = "mic",
- int sample_rate = 48000,
- int num_channels = 2) {
- return BridgeAudioTrack(name, sample_rate, num_channels,
- nullptr, // source
- nullptr, // track
- nullptr, // publication
- nullptr // participant
- );
- }
-};
-
-TEST_F(BridgeAudioTrackTest, AccessorsReturnConstructionValues) {
- auto track = createNullTrack("test-mic", 16000, 1);
-
- EXPECT_EQ(track.name(), "test-mic") << "Name should match construction value";
- EXPECT_EQ(track.sampleRate(), 16000) << "Sample rate should match";
- EXPECT_EQ(track.numChannels(), 1) << "Channel count should match";
-}
-
-TEST_F(BridgeAudioTrackTest, InitiallyNotReleased) {
- auto track = createNullTrack();
-
- EXPECT_FALSE(track.isReleased())
- << "Track should not be released immediately after construction";
-}
-
-TEST_F(BridgeAudioTrackTest, ReleaseMarksTrackAsReleased) {
- auto track = createNullTrack();
-
- track.release();
-
- EXPECT_TRUE(track.isReleased())
- << "Track should be released after calling release()";
-}
-
-TEST_F(BridgeAudioTrackTest, DoubleReleaseIsIdempotent) {
- auto track = createNullTrack();
-
- track.release();
- EXPECT_NO_THROW(track.release())
- << "Calling release() a second time should be a no-op";
- EXPECT_TRUE(track.isReleased());
-}
-
-TEST_F(BridgeAudioTrackTest, PushFrameAfterReleaseReturnsFalse) {
- auto track = createNullTrack();
- track.release();
-
- std::vector data(960, 0);
-
- EXPECT_FALSE(track.pushFrame(data, 480))
- << "pushFrame (vector) on a released track should return false";
-}
-
-TEST_F(BridgeAudioTrackTest, PushFrameRawPointerAfterReleaseReturnsFalse) {
- auto track = createNullTrack();
- track.release();
-
- std::vector data(960, 0);
-
- EXPECT_FALSE(track.pushFrame(data.data(), 480))
- << "pushFrame (raw pointer) on a released track should return false";
-}
-
-TEST_F(BridgeAudioTrackTest, MuteOnReleasedTrackDoesNotCrash) {
- auto track = createNullTrack();
- track.release();
-
- EXPECT_NO_THROW(track.mute())
- << "mute() on a released track should be a no-op";
-}
-
-TEST_F(BridgeAudioTrackTest, UnmuteOnReleasedTrackDoesNotCrash) {
- auto track = createNullTrack();
- track.release();
-
- EXPECT_NO_THROW(track.unmute())
- << "unmute() on a released track should be a no-op";
-}
-
-} // namespace test
-} // namespace livekit_bridge
diff --git a/bridge/tests/test_bridge_video_track.cpp b/bridge/tests/test_bridge_video_track.cpp
deleted file mode 100644
index 08517b02..00000000
--- a/bridge/tests/test_bridge_video_track.cpp
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright 2025 LiveKit
- *
- * 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.
- */
-
-/// @file test_bridge_video_track.cpp
-/// @brief Unit tests for BridgeVideoTrack.
-
-#include
-#include
-
-#include
-#include
-#include
-
-namespace livekit_bridge {
-namespace test {
-
-class BridgeVideoTrackTest : public ::testing::Test {
-protected:
- /// Create a BridgeVideoTrack with null SDK objects for pure-logic testing.
- static BridgeVideoTrack createNullTrack(const std::string &name = "cam",
- int width = 1280, int height = 720) {
- return BridgeVideoTrack(name, width, height,
- nullptr, // source
- nullptr, // track
- nullptr, // publication
- nullptr // participant
- );
- }
-};
-
-TEST_F(BridgeVideoTrackTest, AccessorsReturnConstructionValues) {
- auto track = createNullTrack("test-cam", 640, 480);
-
- EXPECT_EQ(track.name(), "test-cam") << "Name should match construction value";
- EXPECT_EQ(track.width(), 640) << "Width should match";
- EXPECT_EQ(track.height(), 480) << "Height should match";
-}
-
-TEST_F(BridgeVideoTrackTest, InitiallyNotReleased) {
- auto track = createNullTrack();
-
- EXPECT_FALSE(track.isReleased())
- << "Track should not be released immediately after construction";
-}
-
-TEST_F(BridgeVideoTrackTest, ReleaseMarksTrackAsReleased) {
- auto track = createNullTrack();
-
- track.release();
-
- EXPECT_TRUE(track.isReleased())
- << "Track should be released after calling release()";
-}
-
-TEST_F(BridgeVideoTrackTest, DoubleReleaseIsIdempotent) {
- auto track = createNullTrack();
-
- track.release();
- EXPECT_NO_THROW(track.release())
- << "Calling release() a second time should be a no-op";
- EXPECT_TRUE(track.isReleased());
-}
-
-TEST_F(BridgeVideoTrackTest, PushFrameAfterReleaseReturnsFalse) {
- auto track = createNullTrack();
- track.release();
-
- std::vector data(1280 * 720 * 4, 0);
-
- EXPECT_FALSE(track.pushFrame(data))
- << "pushFrame (vector) on a released track should return false";
-}
-
-TEST_F(BridgeVideoTrackTest, PushFrameRawPointerAfterReleaseReturnsFalse) {
- auto track = createNullTrack();
- track.release();
-
- std::vector data(1280 * 720 * 4, 0);
-
- EXPECT_FALSE(track.pushFrame(data.data(), data.size()))
- << "pushFrame (raw pointer) on a released track should return false";
-}
-
-TEST_F(BridgeVideoTrackTest, MuteOnReleasedTrackDoesNotCrash) {
- auto track = createNullTrack();
- track.release();
-
- EXPECT_NO_THROW(track.mute())
- << "mute() on a released track should be a no-op";
-}
-
-TEST_F(BridgeVideoTrackTest, UnmuteOnReleasedTrackDoesNotCrash) {
- auto track = createNullTrack();
- track.release();
-
- EXPECT_NO_THROW(track.unmute())
- << "unmute() on a released track should be a no-op";
-}
-
-} // namespace test
-} // namespace livekit_bridge
diff --git a/bridge/tests/test_livekit_bridge.cpp b/bridge/tests/test_livekit_bridge.cpp
deleted file mode 100644
index 43c8f6fb..00000000
--- a/bridge/tests/test_livekit_bridge.cpp
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright 2025 LiveKit
- *
- * 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.
- */
-
-/// @file test_livekit_bridge.cpp
-/// @brief Unit tests for LiveKitBridge.
-
-#include
-#include
-
-#include
-
-#include
-
-namespace livekit_bridge {
-namespace test {
-
-class LiveKitBridgeTest : public ::testing::Test {
-protected:
- // No SetUp/TearDown needed -- we test the bridge without initializing
- // the LiveKit SDK, since we only exercise pre-connection behaviour.
-};
-
-// ============================================================================
-// Initial state
-// ============================================================================
-
-TEST_F(LiveKitBridgeTest, InitiallyNotConnected) {
- LiveKitBridge bridge;
-
- EXPECT_FALSE(bridge.isConnected())
- << "Bridge should not be connected immediately after construction";
-}
-
-TEST_F(LiveKitBridgeTest, DisconnectBeforeConnectIsNoOp) {
- LiveKitBridge bridge;
-
- EXPECT_NO_THROW(bridge.disconnect())
- << "disconnect() on an unconnected bridge should be a safe no-op";
-
- EXPECT_FALSE(bridge.isConnected());
-}
-
-TEST_F(LiveKitBridgeTest, MultipleDisconnectsAreIdempotent) {
- LiveKitBridge bridge;
-
- EXPECT_NO_THROW({
- bridge.disconnect();
- bridge.disconnect();
- bridge.disconnect();
- }) << "Multiple disconnect() calls should be safe";
-}
-
-TEST_F(LiveKitBridgeTest, DestructorOnUnconnectedBridgeIsSafe) {
- // Just verify no crash when the bridge is destroyed without connecting.
- EXPECT_NO_THROW({
- LiveKitBridge bridge;
- // bridge goes out of scope here
- });
-}
-
-// ============================================================================
-// Track creation before connection
-// ============================================================================
-
-TEST_F(LiveKitBridgeTest, CreateAudioTrackBeforeConnectThrows) {
- LiveKitBridge bridge;
-
- EXPECT_THROW(bridge.createAudioTrack("mic", 48000, 2,
- livekit::TrackSource::SOURCE_MICROPHONE),
- std::runtime_error)
- << "createAudioTrack should throw when not connected";
-}
-
-TEST_F(LiveKitBridgeTest, CreateVideoTrackBeforeConnectThrows) {
- LiveKitBridge bridge;
-
- EXPECT_THROW(bridge.createVideoTrack("cam", 1280, 720,
- livekit::TrackSource::SOURCE_CAMERA),
- std::runtime_error)
- << "createVideoTrack should throw when not connected";
-}
-
-// ============================================================================
-// Callback registration (pre-connection — warns but does not crash)
-// ============================================================================
-
-TEST_F(LiveKitBridgeTest, SetAndClearAudioCallbackBeforeConnectDoesNotCrash) {
- LiveKitBridge bridge;
-
- EXPECT_NO_THROW({
- bridge.setOnAudioFrameCallback("remote-participant",
- livekit::TrackSource::SOURCE_MICROPHONE,
- [](const livekit::AudioFrame &) {});
-
- bridge.clearOnAudioFrameCallback("remote-participant",
- livekit::TrackSource::SOURCE_MICROPHONE);
- }) << "set/clear audio callback before connect should be safe (warns)";
-}
-
-TEST_F(LiveKitBridgeTest, SetAndClearVideoCallbackBeforeConnectDoesNotCrash) {
- LiveKitBridge bridge;
-
- EXPECT_NO_THROW({
- bridge.setOnVideoFrameCallback(
- "remote-participant", livekit::TrackSource::SOURCE_CAMERA,
- [](const livekit::VideoFrame &, std::int64_t) {});
-
- bridge.clearOnVideoFrameCallback("remote-participant",
- livekit::TrackSource::SOURCE_CAMERA);
- }) << "set/clear video callback before connect should be safe (warns)";
-}
-
-TEST_F(LiveKitBridgeTest, ClearNonExistentCallbackIsNoOp) {
- LiveKitBridge bridge;
-
- EXPECT_NO_THROW({
- bridge.clearOnAudioFrameCallback("nonexistent",
- livekit::TrackSource::SOURCE_MICROPHONE);
- bridge.clearOnVideoFrameCallback("nonexistent",
- livekit::TrackSource::SOURCE_CAMERA);
- }) << "Clearing a callback that was never registered should be a no-op";
-}
-
-} // namespace test
-} // namespace livekit_bridge
diff --git a/bridge/tests/test_rpc_controller.cpp b/bridge/tests/test_rpc_controller.cpp
deleted file mode 100644
index be2d0355..00000000
--- a/bridge/tests/test_rpc_controller.cpp
+++ /dev/null
@@ -1,273 +0,0 @@
-/*
- * Copyright 2026 LiveKit
- *
- * 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.
- */
-
-/// @file test_rpc_controller.cpp
-/// @brief Unit tests for RpcController.
-
-#include
-
-#include "livekit_bridge/rpc_constants.h"
-#include "rpc_controller.h"
-
-#include "livekit/local_participant.h"
-#include "livekit/rpc_error.h"
-
-#include
-#include
-
-namespace livekit_bridge {
-namespace test {
-
-// Records (action, track_name) pairs passed to the TrackActionFn callback.
-struct TrackActionRecord {
- std::string action;
- std::string track_name;
-};
-
-class RpcControllerTest : public ::testing::Test {
-protected:
- std::vector recorded_actions_;
-
- std::unique_ptr makeController() {
- namespace tc = rpc::track_control;
- return std::make_unique(
- [this](const tc::Action &action, const std::string &track_name) {
- const char *action_str =
- (action == tc::Action::kActionMute) ? tc::kActionMute
- : tc::kActionUnmute;
- recorded_actions_.push_back({action_str, track_name});
- });
- }
-
- std::unique_ptr makeThrowingController() {
- return std::make_unique(
- [](const rpc::track_control::Action &, const std::string &track_name) {
- throw livekit::RpcError(
- livekit::RpcError::ErrorCode::APPLICATION_ERROR,
- "track not found: " + track_name);
- });
- }
-
- // Helper: call the private handleTrackControlRpc with a given payload.
- std::optional
- callHandler(RpcController &controller, const std::string &payload,
- const std::string &caller = "test-caller") {
- livekit::RpcInvocationData data;
- data.request_id = "test-request-id";
- data.caller_identity = caller;
- data.payload = payload;
- data.response_timeout_sec = 10.0;
- return controller.handleTrackControlRpc(data);
- }
-};
-
-// ============================================================================
-// Construction & lifecycle
-// ============================================================================
-
-TEST_F(RpcControllerTest, InitiallyDisabled) {
- auto controller = makeController();
- EXPECT_FALSE(controller->isEnabled());
-}
-
-TEST_F(RpcControllerTest, DisableOnAlreadyDisabledIsNoOp) {
- auto controller = makeController();
- EXPECT_NO_THROW(controller->disable());
- EXPECT_FALSE(controller->isEnabled());
-}
-
-TEST_F(RpcControllerTest, DisableMultipleTimesIsIdempotent) {
- auto controller = makeController();
- EXPECT_NO_THROW({
- controller->disable();
- controller->disable();
- controller->disable();
- });
-}
-
-TEST_F(RpcControllerTest, DestructorWithoutEnableIsSafe) {
- EXPECT_NO_THROW({ auto controller = makeController(); });
-}
-
-// ============================================================================
-// handleTrackControlRpc — payload parsing
-// ============================================================================
-
-TEST_F(RpcControllerTest, ValidMutePayload) {
- auto controller = makeController();
- auto result = callHandler(*controller, "mute:my-track");
-
- ASSERT_TRUE(result.has_value());
- EXPECT_EQ(result.value(), rpc::track_control::kResponseOk);
-
- ASSERT_EQ(recorded_actions_.size(), 1u);
- EXPECT_EQ(recorded_actions_[0].action, "mute");
- EXPECT_EQ(recorded_actions_[0].track_name, "my-track");
-}
-
-TEST_F(RpcControllerTest, ValidUnmutePayload) {
- auto controller = makeController();
- auto result = callHandler(*controller, "unmute:cam");
-
- ASSERT_TRUE(result.has_value());
- EXPECT_EQ(result.value(), rpc::track_control::kResponseOk);
-
- ASSERT_EQ(recorded_actions_.size(), 1u);
- EXPECT_EQ(recorded_actions_[0].action, "unmute");
- EXPECT_EQ(recorded_actions_[0].track_name, "cam");
-}
-
-TEST_F(RpcControllerTest, TrackNameWithColons) {
- auto controller = makeController();
- auto result = callHandler(*controller, "mute:track:with:colons");
-
- ASSERT_TRUE(result.has_value());
- ASSERT_EQ(recorded_actions_.size(), 1u);
- EXPECT_EQ(recorded_actions_[0].action, "mute");
- EXPECT_EQ(recorded_actions_[0].track_name, "track:with:colons");
-}
-
-TEST_F(RpcControllerTest, TrackNameWithSpaces) {
- auto controller = makeController();
- auto result = callHandler(*controller, "unmute:my track name");
-
- ASSERT_TRUE(result.has_value());
- ASSERT_EQ(recorded_actions_.size(), 1u);
- EXPECT_EQ(recorded_actions_[0].action, "unmute");
- EXPECT_EQ(recorded_actions_[0].track_name, "my track name");
-}
-
-// ============================================================================
-// handleTrackControlRpc — invalid payloads
-// ============================================================================
-
-TEST_F(RpcControllerTest, EmptyPayloadThrows) {
- auto controller = makeController();
- EXPECT_THROW(callHandler(*controller, ""), livekit::RpcError);
- EXPECT_TRUE(recorded_actions_.empty());
-}
-
-TEST_F(RpcControllerTest, NoDelimiterThrows) {
- auto controller = makeController();
- EXPECT_THROW(callHandler(*controller, "mutetrack"), livekit::RpcError);
- EXPECT_TRUE(recorded_actions_.empty());
-}
-
-TEST_F(RpcControllerTest, LeadingDelimiterThrows) {
- auto controller = makeController();
- EXPECT_THROW(callHandler(*controller, ":track"), livekit::RpcError);
- EXPECT_TRUE(recorded_actions_.empty());
-}
-
-TEST_F(RpcControllerTest, UnknownActionThrows) {
- auto controller = makeController();
- EXPECT_THROW(callHandler(*controller, "pause:cam"), livekit::RpcError);
- EXPECT_TRUE(recorded_actions_.empty());
-}
-
-TEST_F(RpcControllerTest, CaseSensitiveAction) {
- auto controller = makeController();
- EXPECT_THROW(callHandler(*controller, "MUTE:cam"), livekit::RpcError);
- EXPECT_THROW(callHandler(*controller, "Mute:cam"), livekit::RpcError);
- EXPECT_TRUE(recorded_actions_.empty());
-}
-
-// ============================================================================
-// handleTrackControlRpc — TrackActionFn propagation
-// ============================================================================
-
-TEST_F(RpcControllerTest, TrackActionFnExceptionPropagates) {
- auto controller = makeThrowingController();
-
- try {
- callHandler(*controller, "mute:nonexistent");
- FAIL() << "Expected RpcError to propagate from TrackActionFn";
- } catch (const livekit::RpcError &e) {
- EXPECT_EQ(e.code(), static_cast(
- livekit::RpcError::ErrorCode::APPLICATION_ERROR));
- EXPECT_NE(std::string(e.message()).find("nonexistent"), std::string::npos)
- << "Error message should contain the track name";
- }
-}
-
-TEST_F(RpcControllerTest, MultipleCallsAccumulate) {
- auto controller = makeController();
-
- callHandler(*controller, "mute:audio");
- callHandler(*controller, "unmute:audio");
- callHandler(*controller, "mute:video");
-
- ASSERT_EQ(recorded_actions_.size(), 3u);
- EXPECT_EQ(recorded_actions_[0].action, "mute");
- EXPECT_EQ(recorded_actions_[0].track_name, "audio");
- EXPECT_EQ(recorded_actions_[1].action, "unmute");
- EXPECT_EQ(recorded_actions_[1].track_name, "audio");
- EXPECT_EQ(recorded_actions_[2].action, "mute");
- EXPECT_EQ(recorded_actions_[2].track_name, "video");
-}
-
-// ============================================================================
-// handleTrackControlRpc — caller identity forwarded
-// ============================================================================
-
-TEST_F(RpcControllerTest, CallerIdentityPassedThrough) {
- auto controller = makeController();
- auto result = callHandler(*controller, "mute:mic", "remote-robot");
-
- ASSERT_TRUE(result.has_value());
- ASSERT_EQ(recorded_actions_.size(), 1u);
- EXPECT_EQ(recorded_actions_[0].action, "mute");
- EXPECT_EQ(recorded_actions_[0].track_name, "mic");
-}
-
-// ============================================================================
-// rpc_constants — formatPayload
-// ============================================================================
-
-TEST_F(RpcControllerTest, FormatPayloadMute) {
- namespace tc = rpc::track_control;
- std::string payload = tc::formatPayload(tc::kActionMute, "cam");
- EXPECT_EQ(payload, "mute:cam");
-}
-
-TEST_F(RpcControllerTest, FormatPayloadUnmute) {
- namespace tc = rpc::track_control;
- std::string payload = tc::formatPayload(tc::kActionUnmute, "mic");
- EXPECT_EQ(payload, "unmute:mic");
-}
-
-TEST_F(RpcControllerTest, FormatPayloadEmptyTrackName) {
- namespace tc = rpc::track_control;
- std::string payload = tc::formatPayload(tc::kActionMute, "");
- EXPECT_EQ(payload, "mute:");
-}
-
-TEST_F(RpcControllerTest, FormatPayloadRoundTrip) {
- namespace tc = rpc::track_control;
- std::string track_name = "some-track-123";
- std::string payload = tc::formatPayload(tc::kActionMute, track_name);
-
- auto controller = makeController();
- auto result = callHandler(*controller, payload);
-
- ASSERT_TRUE(result.has_value());
- ASSERT_EQ(recorded_actions_.size(), 1u);
- EXPECT_EQ(recorded_actions_[0].action, tc::kActionMute);
- EXPECT_EQ(recorded_actions_[0].track_name, track_name);
-}
-
-} // namespace test
-} // namespace livekit_bridge
diff --git a/cmake/protobuf.cmake b/cmake/protobuf.cmake
index 8132a084..3e0fa33c 100644
--- a/cmake/protobuf.cmake
+++ b/cmake/protobuf.cmake
@@ -10,6 +10,7 @@
# - Target protobuf::protoc (on vendored path; on Windows we may only have an executable)
include(FetchContent)
+include(warnings)
option(LIVEKIT_USE_SYSTEM_PROTOBUF "Use system-installed Protobuf instead of vendoring" OFF)
@@ -63,8 +64,13 @@ if(WIN32 AND NOT LIVEKIT_USE_SYSTEM_PROTOBUF)
# Include dirs: prefer the imported target usage requirements.
if(TARGET protobuf::libprotobuf)
get_target_property(_pb_includes protobuf::libprotobuf INTERFACE_INCLUDE_DIRECTORIES)
+ livekit_treat_as_external(protobuf::libprotobuf)
elseif(TARGET protobuf::protobuf) # some protobuf builds use protobuf::protobuf
get_target_property(_pb_includes protobuf::protobuf INTERFACE_INCLUDE_DIRECTORIES)
+ livekit_treat_as_external(protobuf::protobuf)
+ endif()
+ if(TARGET protobuf::protoc)
+ livekit_treat_as_external(protobuf::protoc)
endif()
if(NOT _pb_includes)
# Best-effort fallback: Protobuf_INCLUDE_DIRS is commonly set by ProtobufConfig
@@ -89,6 +95,14 @@ if(LIVEKIT_USE_SYSTEM_PROTOBUF)
if(NOT Protobuf_PROTOC_EXECUTABLE)
find_program(Protobuf_PROTOC_EXECUTABLE NAMES protoc REQUIRED)
endif()
+ if(TARGET protobuf::libprotobuf)
+ livekit_treat_as_external(protobuf::libprotobuf)
+ elseif(TARGET protobuf::protobuf)
+ livekit_treat_as_external(protobuf::protobuf)
+ endif()
+ if(TARGET protobuf::protoc)
+ livekit_treat_as_external(protobuf::protoc)
+ endif()
message(STATUS "Using system protoc: ${Protobuf_PROTOC_EXECUTABLE}")
return()
endif()
@@ -117,6 +131,7 @@ set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
# Disable installs/exports in subprojects (avoids export-set errors)
set(protobuf_INSTALL OFF CACHE BOOL "" FORCE)
set(ABSL_ENABLE_INSTALL OFF CACHE BOOL "" FORCE)
+set(ABSL_PROPAGATE_CXX_STD ON CACHE BOOL "" FORCE)
set(utf8_range_ENABLE_INSTALL OFF CACHE BOOL "" FORCE)
# Force hidden visibility on every target created by the FetchContent
@@ -145,7 +160,11 @@ if(MSVC)
endif()
# Make abseil available first so protobuf can find absl:: targets.
-FetchContent_MakeAvailable(livekit_abseil)
+livekit_fetchcontent_makeavailable(livekit_abseil)
+livekit_collect_targets_in_directory(_livekit_abseil_targets "${livekit_abseil_BINARY_DIR}")
+foreach(_livekit_abseil_target IN LISTS _livekit_abseil_targets)
+ livekit_disable_warnings(${_livekit_abseil_target})
+endforeach()
# Workaround for some abseil flags on Apple Silicon.
if(APPLE AND (CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64"))
@@ -172,7 +191,11 @@ if(NOT TARGET absl::base)
endif()
# Now make protobuf available.
-FetchContent_MakeAvailable(livekit_protobuf)
+livekit_fetchcontent_makeavailable(livekit_protobuf)
+livekit_collect_targets_in_directory(_livekit_protobuf_targets "${livekit_protobuf_BINARY_DIR}")
+foreach(_livekit_protobuf_target IN LISTS _livekit_protobuf_targets)
+ livekit_disable_warnings(${_livekit_protobuf_target})
+endforeach()
# Protobuf targets: modern protobuf exports protobuf::protoc etc.
if(TARGET protobuf::protoc)
@@ -194,7 +217,7 @@ endif()
# Include dirs: prefer target usage; keep this var for your existing CMakeLists.
if(TARGET protobuf::libprotobuf)
- get_target_property(_pb_includes protobuf::libprotobuf INTERFACE_INCLUDE_DIRECTORIES)
+ livekit_get_interface_includes(protobuf::libprotobuf _pb_includes)
endif()
if(NOT _pb_includes)
set(_pb_includes "${livekit_protobuf_SOURCE_DIR}/src")
diff --git a/cmake/spdlog.cmake b/cmake/spdlog.cmake
index 0ef0e191..6a22b771 100644
--- a/cmake/spdlog.cmake
+++ b/cmake/spdlog.cmake
@@ -47,6 +47,7 @@ endif()
message(STATUS "LiveKit compile-time log level: ${_LK_LOG_LEVEL_UPPER} (SPDLOG_ACTIVE_LEVEL=${_SPDLOG_ACTIVE_LEVEL})")
include(FetchContent)
+include(warnings)
set(LIVEKIT_SPDLOG_VERSION "1.15.1" CACHE STRING "Vendored spdlog version")
@@ -61,6 +62,9 @@ endif()
# ---------------------------------------------------------------------------
if(WIN32 AND LIVEKIT_USE_VCPKG)
find_package(spdlog CONFIG REQUIRED)
+ if(TARGET spdlog::spdlog)
+ livekit_treat_as_external(spdlog::spdlog)
+ endif()
message(STATUS "Windows: using vcpkg spdlog")
return()
endif()
@@ -79,7 +83,11 @@ set(SPDLOG_BUILD_EXAMPLE OFF CACHE BOOL "" FORCE)
set(SPDLOG_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(SPDLOG_INSTALL OFF CACHE BOOL "" FORCE)
-FetchContent_MakeAvailable(livekit_spdlog)
+livekit_fetchcontent_makeavailable(livekit_spdlog)
+livekit_collect_targets_in_directory(_livekit_spdlog_targets "${livekit_spdlog_BINARY_DIR}")
+foreach(_livekit_spdlog_target IN LISTS _livekit_spdlog_targets)
+ livekit_disable_warnings(${_livekit_spdlog_target})
+endforeach()
# spdlog is linked PRIVATE into liblivekit and must not leak its symbols into
# the SDK's exported ABI. Force hidden visibility on the spdlog target so its
diff --git a/cmake/warnings.cmake b/cmake/warnings.cmake
new file mode 100644
index 00000000..aa450333
--- /dev/null
+++ b/cmake/warnings.cmake
@@ -0,0 +1,109 @@
+# Copyright 2026 LiveKit
+#
+# 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.
+
+# Resolves target aliases, marks their exported include directories as system includes,
+# and disables compiler warnings on compilable third-party targets.
+function(_livekit_resolve_target target out_var)
+ if(NOT TARGET ${target})
+ set(${out_var} "" PARENT_SCOPE)
+ return()
+ endif()
+
+ get_target_property(_aliased_target ${target} ALIASED_TARGET)
+ if(_aliased_target)
+ set(${out_var} "${_aliased_target}" PARENT_SCOPE)
+ else()
+ set(${out_var} "${target}" PARENT_SCOPE)
+ endif()
+endfunction()
+
+function(livekit_disable_warnings target)
+ _livekit_resolve_target(${target} _resolved_target)
+ if(NOT _resolved_target)
+ return()
+ endif()
+
+ get_target_property(_target_type ${_resolved_target} TYPE)
+ get_target_property(_is_imported ${_resolved_target} IMPORTED)
+ if(_is_imported OR _target_type STREQUAL "INTERFACE_LIBRARY")
+ return()
+ endif()
+ if(NOT _target_type MATCHES "^(STATIC_LIBRARY|SHARED_LIBRARY|MODULE_LIBRARY|OBJECT_LIBRARY|EXECUTABLE)$")
+ return()
+ endif()
+
+ target_compile_options(${_resolved_target} PRIVATE
+ $<$:/W0>
+ $<$:/W0>
+ $<$:-w>
+ $<$:-w>
+ )
+endfunction()
+
+function(livekit_mark_system_includes target)
+ _livekit_resolve_target(${target} _resolved_target)
+ if(NOT _resolved_target)
+ return()
+ endif()
+
+ get_target_property(_interface_includes ${_resolved_target} INTERFACE_INCLUDE_DIRECTORIES)
+ if(_interface_includes)
+ set_property(TARGET ${_resolved_target} APPEND PROPERTY
+ INTERFACE_SYSTEM_INCLUDE_DIRECTORIES ${_interface_includes}
+ )
+ endif()
+endfunction()
+
+function(livekit_get_interface_includes target out_var)
+ _livekit_resolve_target(${target} _resolved_target)
+ if(NOT _resolved_target)
+ set(${out_var} "" PARENT_SCOPE)
+ return()
+ endif()
+
+ get_target_property(_interface_includes ${_resolved_target} INTERFACE_INCLUDE_DIRECTORIES)
+ if(_interface_includes)
+ set(${out_var} ${_interface_includes} PARENT_SCOPE)
+ else()
+ set(${out_var} "" PARENT_SCOPE)
+ endif()
+endfunction()
+
+function(livekit_fetchcontent_makeavailable)
+ set(CMAKE_WARN_DEPRECATED OFF)
+ set(CMAKE_POLICY_VERSION_MINIMUM 3.10)
+ FetchContent_MakeAvailable(${ARGV})
+endfunction()
+
+function(livekit_collect_targets_in_directory out_var directory)
+ get_property(_targets DIRECTORY "${directory}" PROPERTY BUILDSYSTEM_TARGETS)
+ get_property(_subdirectories DIRECTORY "${directory}" PROPERTY SUBDIRECTORIES)
+
+ set(_all_targets ${_targets})
+ foreach(_subdirectory IN LISTS _subdirectories)
+ livekit_collect_targets_in_directory(_subdirectory_targets "${_subdirectory}")
+ list(APPEND _all_targets ${_subdirectory_targets})
+ endforeach()
+
+ set(${out_var} ${_all_targets} PARENT_SCOPE)
+endfunction()
+
+function(livekit_treat_as_external target)
+ if(NOT TARGET ${target})
+ return()
+ endif()
+
+ livekit_mark_system_includes(${target})
+ livekit_disable_warnings(${target})
+endfunction()
diff --git a/cpp-example-collection b/cpp-example-collection
index 56815733..008808dc 160000
--- a/cpp-example-collection
+++ b/cpp-example-collection
@@ -1 +1 @@
-Subproject commit 56815733a71c14692569e8adf2916a56a14d4882
+Subproject commit 008808dcda21bcb188295425908219e64eae395d
diff --git a/docker/Dockerfile.sdk b/docker/Dockerfile.sdk
index 8d2ad155..6d31a878 100644
--- a/docker/Dockerfile.sdk
+++ b/docker/Dockerfile.sdk
@@ -28,7 +28,6 @@ WORKDIR /client-sdk-cpp
RUN mkdir -p /client-sdk-cpp
COPY src /client-sdk-cpp/src
COPY include /client-sdk-cpp/include
-COPY bridge /client-sdk-cpp/bridge
COPY build.sh /client-sdk-cpp/build.sh
COPY CMakePresets.json /client-sdk-cpp/CMakePresets.json
COPY build.cmd /client-sdk-cpp/build.cmd
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 00000000..fd06e209
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,14 @@
+# Repository Documentation
+
+Additional documentation for the SDK.
+
+- [Building](building.md) — prerequisites, build scripts, CMake presets,
+ vcpkg, Docker, integration into your CMake project, troubleshooting.
+- [Logging](logging.md) — compile-time vs runtime filtering, log levels,
+ custom sinks (file, JSON, ROS2 `RCLCPP_*` macros).
+- [Tracing](tracing.md) — Chromium-format performance traces, viewing in
+ `chrome://tracing` and Perfetto.
+- [Testing](testing.md) — unit, integration, and stress test suites; env
+ vars; token-helper script for local `livekit-server --dev` runs.
+- [Developer tools](tools.md) — `clang-tidy`, `clang-format`, `valgrind`,
+ Doxygen, pre-commit hook, Rust submodule recovery tips.
diff --git a/docs/building.md b/docs/building.md
new file mode 100644
index 00000000..60f08a71
--- /dev/null
+++ b/docs/building.md
@@ -0,0 +1,366 @@
+# Building
+
+This document covers everything you need to build the LiveKit C++ SDK from
+source: prerequisites, cloning the repository, the build scripts, advanced
+CMake/vcpkg flows, and Docker.
+
+## Prerequisites
+
+### Common to all platforms
+
+- **CMake** ≥ 3.20
+- **Rust / Cargo** — latest stable toolchain (for building the Rust FFI layer).
+ Install via [rustup](https://rustup.rs/).
+- **Git LFS** — required for examples that pull test media assets.
+- **Protobuf** ≥ 5.29
+- **Abseil** — always required (used by Protobuf 5.x+)
+
+### Platform-specific toolchains
+
+| Platform | Compiler | Package manager |
+|----------|----------|-----------------|
+| Windows | Visual Studio 2019+ (MSBuild or Ninja) | vcpkg (see below) |
+| Linux | GCC 9+ or Clang 10+ | `apt` / `dnf` (or vcpkg) |
+| macOS | Xcode 12+ (macOS 12.3+ for ScreenCaptureKit) | Homebrew (or vcpkg) |
+
+## Clone the repository
+
+The SDK depends on the [`client-sdk-rust`](https://github.com/livekit/rust-sdks)
+submodule (recursive), so always clone with submodules:
+
+```bash
+# Option 1: clone with submodules in one step
+git clone --recurse-submodules https://github.com/livekit/client-sdk-cpp.git
+
+# Option 2: clone first, then initialize submodules
+git clone https://github.com/livekit/client-sdk-cpp.git
+cd client-sdk-cpp
+git submodule update --init --recursive
+
+# Pull Git LFS assets if you want to run the integration tests:
+git lfs pull
+```
+
+## Recommended setup
+
+These are the exact packages our CI uses. They will also work for examples.
+
+### macOS
+
+```bash
+brew install cmake ninja protobuf abseil rust
+```
+
+### Ubuntu / Debian
+
+```bash
+sudo apt update && sudo apt install -y \
+ build-essential cmake ninja-build pkg-config \
+ llvm-dev libclang-dev clang \
+ libprotobuf-dev protobuf-compiler libabsl-dev \
+ libssl-dev
+
+# Install Rust if you don't already have it
+curl https://sh.rustup.rs -sSf | sh
+```
+
+If you plan to build the [example collection](https://github.com/livekit-examples/cpp-example-collection)
+(SDL-based renderer + camera/mic capture), also install:
+
+```bash
+sudo apt install -y \
+ libva-dev libdrm-dev libgbm-dev libx11-dev libgl1-mesa-dev \
+ libxext-dev libxcomposite-dev libxdamage-dev libxfixes-dev \
+ libxrandr-dev libxi-dev libxkbcommon-dev \
+ libasound2-dev libpulse-dev \
+ libwayland-dev libdecor-0-dev
+```
+
+### Windows
+
+```powershell
+# Set VCPKG_ROOT once and bootstrap vcpkg
+git clone https://github.com/microsoft/vcpkg.git
+.\vcpkg\bootstrap-vcpkg.bat
+$env:VCPKG_ROOT = "$PWD\vcpkg"
+```
+
+CMake's vcpkg manifest mode (below) reads
+`vcpkg.json` and installs the rest automatically the first time you configure.
+
+## Build scripts (recommended)
+
+The repo ships with `build.sh` (Linux/macOS) and `build.cmd` (Windows) that
+wrap the right CMake preset for your platform and pick sensible defaults.
+
+**Linux/macOS:**
+```bash
+./build.sh release # Build Release
+./build.sh debug # Build Debug
+./build.sh release-examples # Release + examples
+./build.sh debug-examples # Debug + examples
+./build.sh release-tests # Release + tests
+./build.sh debug-tests # Debug + tests
+./build.sh release-all # Release + tests + examples
+./build.sh debug-all # Debug + tests + examples
+./build.sh clean # Clean CMake build artifacts + local-install
+./build.sh clean-all # Deep clean (C++ + Rust + local-install + generated files)
+```
+
+**Windows:**
+```powershell
+.\build.cmd release
+.\build.cmd debug
+.\build.cmd release-examples
+# ... same suffixes as build.sh
+```
+
+The build scripts pass an explicit job count to `cmake --build --parallel`. Set
+`CMAKE_BUILD_PARALLEL_LEVEL` to override the auto-detected logical CPU count.
+
+## Advanced: CMake presets
+
+For more control, drive CMake directly via the presets in
+[CMakePresets.json](https://github.com/livekit/client-sdk-cpp/blob/main/CMakePresets.json):
+
+```bash
+# Linux
+cmake --preset linux-release
+cmake --build --preset linux-release
+
+# macOS
+cmake --preset macos-release
+cmake --build --preset macos-release
+
+# Windows
+cmake --preset windows-release
+cmake --build --preset windows-release
+```
+
+Windows requires `VCPKG_ROOT` to be set:
+
+```powershell
+$env:VCPKG_ROOT = "C:\path\to\vcpkg"
+```
+
+## Advanced: vcpkg manifest mode
+
+vcpkg will automatically install all dependencies listed in
+[vcpkg.json](https://github.com/livekit/client-sdk-cpp/blob/main/vcpkg.json) the first time you configure with its toolchain
+file.
+
+**Windows:**
+```powershell
+cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=$env:VCPKG_ROOT\scripts\buildsystems\vcpkg.cmake
+cmake --build build --config Release
+
+# With examples:
+cmake -B build -S . `
+ -DCMAKE_TOOLCHAIN_FILE=$env:VCPKG_ROOT\scripts\buildsystems\vcpkg.cmake `
+ -DLIVEKIT_BUILD_EXAMPLES=ON -DVCPKG_MANIFEST_FEATURES=examples
+cmake --build build --config Release
+```
+
+**Linux/macOS:**
+```bash
+cmake -B build -S . \
+ -DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake \
+ -DCMAKE_BUILD_TYPE=Release
+cmake --build build
+
+# With examples:
+cmake -B build -S . \
+ -DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DLIVEKIT_BUILD_EXAMPLES=ON -DVCPKG_MANIFEST_FEATURES=examples
+cmake --build build
+```
+
+## Building with Docker
+
+The Docker setup is split into a reusable base image (toolchain + system
+deps) and an SDK image layered on top. **Tested on Linux only.**
+
+```bash
+docker build -t livekit-cpp-sdk-base . -f docker/Dockerfile.base
+docker build --build-arg BASE_IMAGE=livekit-cpp-sdk-base \
+ -t livekit-cpp-sdk . -f docker/Dockerfile.sdk
+docker run -it --network host livekit-cpp-sdk:latest bash
+```
+
+If you're authoring your own Dockerfile, mirror the `ENV` block in
+[docker/Dockerfile.base](https://github.com/livekit/client-sdk-cpp/blob/main/docker/Dockerfile.base):
+
+```bash
+export CC=$HOME/gcc-14/bin/gcc
+export CXX=$HOME/gcc-14/bin/g++
+export LD_LIBRARY_PATH=$HOME/gcc-14/lib64:$LD_LIBRARY_PATH
+export PATH=$HOME/.cargo/bin:$PATH
+export PATH=$HOME/cmake-3.31/bin:$PATH
+```
+
+## CMake options
+
+| Option | Default | Description |
+|--------|---------|-------------|
+| `LIVEKIT_BUILD_EXAMPLES` | OFF | Build example applications |
+| `LIVEKIT_USE_SYSTEM_PROTOBUF` | OFF | Use system Protobuf instead of vcpkg's |
+| `LIVEKIT_LOG_LEVEL` | `TRACE` | Compile-time log threshold (see [logging.md](logging.md)) |
+| `LIVEKIT_VERSION` | repo-derived | SDK version string baked into the binary |
+
+## Build output
+
+After a successful build:
+
+```
+build-release/
+├── lib/
+│ ├── liblivekit.{a,lib} # Main SDK static library
+│ ├── liblivekit_ffi.{so,dylib} # Rust FFI dynamic library
+│ └── livekit_ffi.dll, *.lib # (Windows) FFI DLL + import lib
+├── include/ # Public headers (auto-synced)
+│ └── livekit/
+└── bin/ # Example/test executables
+ └── liblivekit_ffi.{so,dylib} # (Linux/macOS: copied for runtime)
+```
+
+## Integrating into your project
+
+### Using CMake
+
+```cmake
+# Method 1: as a subdirectory
+add_subdirectory(path/to/client-sdk-cpp)
+target_link_libraries(your_target PRIVATE livekit)
+
+# Method 2: find_package (after install)
+find_package(livekit REQUIRED)
+target_link_libraries(your_target PRIVATE livekit)
+```
+
+### Using prebuilt releases
+
+The easiest way to consume the SDK without building from source is via
+the [cpp-example-collection](https://github.com/livekit-examples/cpp-example-collection)
+helper, which downloads a release tarball at CMake configure time:
+
+```cmake
+include(LiveKitSDK.cmake) # pins or auto-resolves a release
+```
+
+See the example collection's
+[`LiveKitSDK.cmake`](https://github.com/livekit-examples/cpp-example-collection/blob/main/cmake/LiveKitSDK.cmake)
+for the full pattern.
+
+### Manual linking
+
+1. Add include path: `build-release/include`
+2. Link the static SDK library:
+ - Windows: `build-release/lib/livekit.lib`
+ - Linux: `build-release/lib/liblivekit.a`
+ - macOS: `build-release/lib/liblivekit.a`
+3. Link/deploy the Rust FFI dynamic library:
+ - Windows: link `livekit_ffi.dll.lib`, deploy `livekit_ffi.dll` next to your `.exe`
+ - Linux: deploy `liblivekit_ffi.so` next to your executable
+ - macOS: deploy `liblivekit_ffi.dylib` next to your executable
+4. Link platform system libraries (see below).
+
+> **Important:** On Linux/macOS the FFI shared library must live next to the
+> executable. RPATH is set to `$ORIGIN` (Linux) / `@loader_path` (macOS).
+
+**Windows system libraries:**
+```
+ntdll userenv winmm iphlpapi msdmo dmoguids wmcodecdspuuid
+ws2_32 secur32 bcrypt crypt32
+```
+
+**macOS frameworks:**
+```
+CoreAudio AudioToolbox CoreFoundation Security CoreGraphics
+CoreMedia VideoToolbox AVFoundation CoreVideo Foundation
+AppKit QuartzCore OpenGL IOSurface Metal MetalKit ScreenCaptureKit
+```
+
+**Linux libraries:**
+```
+OpenSSL::SSL OpenSSL::Crypto
+```
+
+### Runtime dependencies of prebuilt artifacts
+
+Whether protobuf / abseil / openssl need to be installed on the target
+machine depends on how the SDK binary was built:
+
+- **Windows** release artifacts use vcpkg triplet `x64-windows-static-md` —
+ protobuf and abseil are statically linked into the DLLs; no runtime install
+ needed.
+- **macOS** release artifacts (from our CI) do **not** dynamically depend on
+ protobuf / abseil / openssl. You can verify with `otool -L liblivekit.dylib`.
+- **Linux** depends on packaging. Check with `ldd liblivekit_ffi.so`; if any
+ of those are listed, install the corresponding `-dev` (build) or runtime
+ package (`libprotobuf32` / `libabsl` / `libssl3`) as appropriate.
+
+## Troubleshooting
+
+### Missing proto files or `client-sdk-rust` directory
+
+Initialize submodules:
+```bash
+git submodule update --init --recursive
+```
+
+### Deprecated-declaration errors on Linux
+
+Newer GCC versions (12+) are stricter with the WebRTC legacy code in the
+Rust submodule. If `./build.sh release` errors with `-Werror=deprecated-declarations`,
+relax it for the build:
+
+```bash
+export CXXFLAGS="-Wno-deprecated-declarations"
+export CFLAGS="-Wno-deprecated-declarations"
+```
+
+### Rust bindgen fails with "unable to find libclang"
+
+Install `libclang-dev` (Ubuntu) or `llvm` (macOS Homebrew). bindgen normally
+discovers libclang from the system paths once `libclang-dev` is installed; if
+not, point `LIBCLANG_PATH` at your LLVM's `lib` directory (e.g.
+`/usr/lib/llvm-18/lib` on Ubuntu 24.04).
+
+### Rust code recompiles after C++ edits
+
+This was a historical issue; Rust only recompiles now when Rust source files
+change or the Rust library is missing.
+
+### Cannot find Protobuf or other dependencies
+
+Make sure you're passing the vcpkg toolchain file:
+```bash
+-DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake
+```
+
+### `clang-tidy` on Windows
+
+Not currently supported via our scripts — the Visual Studio (MSBuild) CMake
+generator doesn't produce `compile_commands.json`. The Ninja generator does;
+see [tools.md](tools.md).
+
+### How do I deep-clean?
+
+```bash
+./build.sh clean-all # C++ + Rust + local-install + generated files
+```
+
+Or via CMake targets:
+
+```bash
+cmake --build build --target clean # CMake artifacts
+cmake --build build --target cargo_clean # Rust artifacts
+cmake --build build --target clean_generated # Generated protobuf headers
+cmake --build build --target clean_all # Full clean
+```
+
+## Support
+
+- GitHub Issues:
+- LiveKit Docs:
diff --git a/docs/doxygen/index.md b/docs/doxygen/index.md
deleted file mode 100644
index 56df9d37..00000000
--- a/docs/doxygen/index.md
+++ /dev/null
@@ -1,135 +0,0 @@
-# Overview
-
-Build real-time audio/video applications in C++ with LiveKit.
-
-## Quick Start
-
-```cpp
-#include "livekit/livekit.h"
-
-bool initializeLivekit(const std::string& url, const std::string& token) {
- // Init LiveKit
- livekit::initialize(livekit::LogLevel::Info, livekit::LogSink::kConsole);
-
- room_ = std::make_unique();
- livekit::RoomOptions options;
- options.auto_subscribe = true;
- options.dynacast = false;
- if (!room_->connect(url, token, options)) {
- std::cerr << "Failed to connect\n";
- livekit::shutdown();
- return false;
- }
-
- std::cout << "Connected.\n";
-
- // ---- Create & publish AUDIO ----
- // Note: Hook up your own audio capture flow to |audioSource_|
- audioSource_ = std::make_shared(48000, 1, 10);
- auto audioTrack = livekit::LocalAudioTrack::createLocalAudioTrack("noise", audioSource_);
-
- livekit::TrackPublishOptions audioOpts;
- audioOpts.source = livekit::TrackSource::SOURCE_MICROPHONE;
-
- try {
- audioPub_ = room_->localParticipant()->publishTrack(audioTrack, audioOpts);
- std::cout << "Published audio: sid=" << audioPub_->sid() << "\n";
- } catch (const std::exception& e) {
- std::cerr << "Failed to publish audio: " << e.what() << "\n";
- return false;
- }
-
- // ---- Create & publish VIDEO ----
- // Note: Hook up your own video capture flow to |videoSource_|
- videoSource_ = std::make_shared(1280, 720);
- auto videoTrack = livekit::LocalVideoTrack::createLocalVideoTrack("rgb", videoSource_);
-
- livekit::TrackPublishOptions videoOpts;
- videoOpts.source = livekit::TrackSource::SOURCE_CAMERA;
-
- try {
- videoPub_ = room_->localParticipant()->publishTrack(videoTrack, videoOpts);
- std::cout << "Published video: sid=" << videoPub_->sid() << "\n";
- } catch (const std::exception& e) {
- std::cerr << "Failed to publish video: " << e.what() << "\n";
- return false;
- }
- return true;
-}
-
-void shutdownLivekit() {
- // Best-effort unpublish
- try {
- if (room_ && audioPub_)
- room_->localParticipant()->unpublishTrack(audioPub_->sid());
- if (room_ && videoPub_)
- room_->localParticipant()->unpublishTrack(videoPub_->sid());
- } catch (...) {
- }
-
- audioPub_.reset();
- videoPub_.reset();
- audioSource_.reset();
- videoSource_.reset();
- room_.reset();
-
- livekit::shutdown();
-}
-```
-
-## Key Classes
-
-| Class | Description |
-|-------|-------------|
-| @ref livekit::Room | Main entry point - connect to a LiveKit room |
-| @ref livekit::RoomOptions | Configuration for room connection (auto_subscribe, dynacast, etc.) |
-| @ref livekit::LocalParticipant | The local user - publish tracks and send data |
-| @ref livekit::RemoteParticipant | Other participants in the room |
-| @ref livekit::AudioSource | Audio input source for publishing (sample rate, channels) |
-| @ref livekit::VideoSource | Video input source for publishing (width, height) |
-| @ref livekit::LocalAudioTrack | Local audio track created from AudioSource |
-| @ref livekit::LocalVideoTrack | Local video track created from VideoSource |
-| @ref livekit::LocalTrackPublication | Handle to a published local track |
-| @ref livekit::TrackPublishOptions | Options for publishing (source type, codec, etc.) |
-| @ref livekit::AudioStream | Receive audio from remote participants |
-| @ref livekit::VideoStream | Receive video from remote participants |
-| @ref livekit::RoomDelegate | Callbacks for room events |
-
-## Installation
-
-See the [GitHub README](https://github.com/livekit/client-sdk-cpp#readme) for build instructions.
-
-**Requirements:**
-
-- CMake ≥ 3.20
-- Rust/Cargo (latest stable)
-- Platform: Windows, macOS, or Linux
-
-## Examples
-
-**Getting started**
-
-- [SimpleRoom](https://github.com/livekit-examples/cpp-example-collection/tree/main/simple_room) - Minimal room connection that publishes audio and video.
-- [BasicRoom](https://github.com/livekit-examples/cpp-example-collection/tree/main/basic_room) - Publishes synthetic noise audio plus an RGB test pattern with capture loops; runs until Ctrl-C.
-- [HelloLiveKit](https://github.com/livekit-examples/cpp-example-collection/tree/main/hello_livekit) - Two-process sender/receiver demo of publishing video and a data track from one app and subscribing in another.
-
-**Logging**
-
-- [LoggingLevels](https://github.com/livekit-examples/cpp-example-collection/tree/main/logging_levels) - Demonstrates @ref livekit::setLogLevel() and @ref livekit::setLogCallback(), including custom sinks for redirecting SDK logs.
-
-**RPC and data**
-
-- [SimpleRpc](https://github.com/livekit-examples/cpp-example-collection/tree/main/simple_rpc) - Remote procedure calls between participants.
-- [SimpleDataStream](https://github.com/livekit-examples/cpp-example-collection/tree/main/simple_data_stream) - Send and receive text and binary data streams.
-- [PingPong](https://github.com/livekit-examples/cpp-example-collection/tree/main/ping_pong) - Two-process round-trip over data tracks that prints RTT and one-way latency metrics.
-- [SimpleJoystick](https://github.com/livekit-examples/cpp-example-collection/tree/main/simple_joystick) - Interactive sender/receiver: keyboard-driven joystick commands delivered via RPC, with auto-reconnect.
-
-**Advanced video**
-
-- [UserTimestampedVideo](https://github.com/livekit-examples/cpp-example-collection/tree/main/user_timestamped_video) - Producer/consumer pair showing per-frame `VideoFrameMetadata::user_timestamp_us` and the rich `setOnVideoFrameEventCallback` vs. legacy `setOnVideoFrameCallback` paths.
-
-## Resources
-
-- [GitHub Repository](https://github.com/livekit/client-sdk-cpp)
-- [LiveKit Documentation](https://docs.livekit.io/)
-- [Community Slack](https://livekit.io/join-slack)
diff --git a/docs/logging.md b/docs/logging.md
new file mode 100644
index 00000000..1889479c
--- /dev/null
+++ b/docs/logging.md
@@ -0,0 +1,85 @@
+# Logging
+
+The SDK uses [spdlog](https://github.com/gabime/spdlog) internally but does
+**not** expose it in public headers. All log output goes through a thin public
+API in ``.
+
+## Two-tier filtering
+
+| Tier | When | How | Cost |
+|------|------|-----|------|
+| **Compile-time** | CMake configure | `-DLIVEKIT_LOG_LEVEL=WARN` | Zero — calls below the level are stripped from the binary |
+| **Runtime** | Any time after `initialize()` | `livekit::setLogLevel(LogLevel::Warn)` | Minimal — a level check before formatting |
+
+### Compile-time level (`LIVEKIT_LOG_LEVEL`)
+
+Set once when you configure CMake. Calls below this threshold are completely
+removed by the preprocessor — no format-string evaluation, no function call.
+
+```bash
+# Development (default): keep everything available
+cmake -DLIVEKIT_LOG_LEVEL=TRACE ..
+
+# Release: strip TRACE / DEBUG / INFO
+cmake -DLIVEKIT_LOG_LEVEL=WARN ..
+
+# Production: only ERROR and CRITICAL survive
+cmake -DLIVEKIT_LOG_LEVEL=ERROR ..
+```
+
+Valid values: `TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR`, `CRITICAL`, `OFF`.
+
+### Runtime level (`setLogLevel`)
+
+Among the levels that survived compilation you can still filter at runtime
+without rebuilding:
+
+```cpp
+#include
+
+livekit::initialize(); // default level: Info
+livekit::setLogLevel(livekit::LogLevel::Debug); // show more detail
+livekit::setLogLevel(livekit::LogLevel::Warn); // suppress info chatter
+```
+
+## Custom log callback
+
+Replace the default stderr sink with your own handler. This is the integration
+point for frameworks like ROS2 (`RCLCPP_*` macros), Android logcat, or any
+structured-logging pipeline:
+
+```cpp
+#include
+
+livekit::initialize();
+livekit::setLogLevel(livekit::LogLevel::Trace);
+
+livekit::setLogCallback(
+ [](livekit::LogLevel level,
+ const std::string &logger_name,
+ const std::string &message) {
+ // Route to your framework, e.g.:
+ // RCLCPP_INFO(get_logger(), "[%s] %s", logger_name.c_str(), message.c_str());
+ myLogger.log(level, logger_name, message);
+ });
+
+// Pass nullptr to restore the default stderr sink:
+livekit::setLogCallback(nullptr);
+```
+
+See the [`logging_levels/custom_sinks.cpp`](https://github.com/livekit-examples/cpp-example-collection/blob/main/logging_levels/custom_sinks.cpp)
+example for three copy-paste-ready patterns: a **file logger**, **JSON
+structured lines**, and a **ROS2 bridge** that maps `LogLevel` to `RCLCPP_*`
+macros.
+
+## Available log levels
+
+| Level | Typical use |
+|-------|-------------|
+| `Trace` | Per-frame / per-packet detail (very noisy) |
+| `Debug` | Diagnostic info useful during development |
+| `Info` | Normal operational messages (connection, track events) |
+| `Warn` | Unexpected but recoverable situations |
+| `Error` | Failures that affect functionality |
+| `Critical` | Unrecoverable errors |
+| `Off` | Suppress all output |
diff --git a/docs/testing.md b/docs/testing.md
new file mode 100644
index 00000000..120baabb
--- /dev/null
+++ b/docs/testing.md
@@ -0,0 +1,112 @@
+# Testing
+
+The SDK includes integration and stress tests using
+[Google Test](https://github.com/google/googletest).
+
+## Building the test binaries
+
+**Linux/macOS:**
+```bash
+./build.sh debug-tests # Build Debug with tests
+./build.sh release-tests # Build Release with tests
+```
+
+**Windows:**
+```powershell
+.\build.cmd debug-tests
+.\build.cmd release-tests
+```
+
+## Running tests
+
+After building, run tests using `ctest` or invoke the binaries directly:
+
+```bash
+# Run all tests via ctest
+cd build-debug
+ctest --output-on-failure
+
+# Or run test executables directly
+./build-debug/bin/livekit_integration_tests
+./build-debug/bin/livekit_stress_tests
+
+# Run specific test suites
+./build-debug/bin/livekit_integration_tests --gtest_filter="*Rpc*"
+./build-debug/bin/livekit_stress_tests --gtest_filter="*MaxPayloadStress*"
+```
+
+__Note:__ The tests require tokens and a running LiveKit server. See the section below for details.
+
+## Test binaries
+
+| Executable | Description |
+|------------|-------------|
+| `livekit_unit_tests` | Pure unit tests (no server required) |
+| `livekit_integration_tests` | Quick tests (~1-2 minutes) for SDK functionality |
+| `livekit_stress_tests` | Long-running tests (configurable, default 1 hour) |
+
+## Running a local LiveKit server for tests
+
+The integration and stress suites need a running LiveKit server. The easiest
+path is `livekit-server --dev`, which uses the well-known dev API
+key/secret (`devkey` / `secret`).
+
+Install [`livekit-server`](https://docs.livekit.io/home/self-hosting/local/)
+and start it with data tracks enabled:
+
+```bash
+livekit-server --dev
+```
+
+## Environment variables
+
+The integration and stress test suites (data tracks, RPC, media multistream,
+etc.) require a server URL and two participant tokens:
+
+```bash
+# Required
+export LIVEKIT_URL="ws://localhost:7880" # or wss://your-server.livekit.cloud
+export LIVEKIT_TOKEN_A=""
+export LIVEKIT_TOKEN_B=""
+
+# Optional (for stress tests)
+export RPC_STRESS_DURATION_SECONDS=3600 # Test duration (default: 1 hour)
+export RPC_STRESS_CALLER_THREADS=4 # Concurrent caller threads (default: 4)
+```
+
+### Generating tokens for the test suites
+
+The easiest path is to source the helper script, which mints both
+participant tokens against a local `livekit-server --dev` and exports
+`LIVEKIT_TOKEN_A`, `LIVEKIT_TOKEN_B`, and `LIVEKIT_URL` for the current shell:
+
+```bash
+source .token_helpers/set_data_track_test_tokens.bash
+```
+
+To generate tokens manually (e.g. against a non-default server), install
+[`livekit-cli`](https://docs.livekit.io/home/cli/cli-setup/) and run:
+
+```bash
+export LIVEKIT_TOKEN_A="$(lk token create --api-key devkey --api-secret secret -i cpp-test-a \
+ --join --valid-for 99999h --room cpp_data_track_test \
+ --grant '{"canPublish":true,"canSubscribe":true,"canPublishData":true}' \
+ --token-only)"
+export LIVEKIT_TOKEN_B="$(lk token create --api-key devkey --api-secret secret -i cpp-test-b \
+ --join --valid-for 99999h --room cpp_data_track_test \
+ --grant '{"canPublish":true,"canSubscribe":true,"canPublishData":true}' \
+ --token-only)"
+```
+
+## Test coverage
+
+- **SDK initialization**: initialize / shutdown lifecycle.
+- **Room**: room creation, options, connection.
+- **Audio frame**: frame creation, manipulation, edge cases.
+- **RPC**: round-trip calls, max payload (15 KB), timeouts, errors, concurrent calls.
+- **Stress**: high throughput, bidirectional RPC, memory pressure.
+
+## Memory checks (valgrind)
+
+Run `valgrind` against the test binaries to check for memory leaks and other
+issues. See [tools.md](tools.md) for the recipe.
diff --git a/docs/tools.md b/docs/tools.md
new file mode 100644
index 00000000..b67826f5
--- /dev/null
+++ b/docs/tools.md
@@ -0,0 +1,163 @@
+# Developer tools
+
+This SDK uses several tools and checks to enforce code quality. All of these
+are also enforced in CI on PRs.
+
+## Clang tools
+
+- **`clang-tidy`** — static analysis. See [.clang-tidy](https://github.com/livekit/client-sdk-cpp/blob/main/.clang-tidy) for the
+ enabled checks. Enforced in CI on PR.
+- **`clang-format`** — code formatting and style consistency. See
+ [.clang-format](https://github.com/livekit/client-sdk-cpp/blob/main/.clang-format) for the rules. Enforced in CI on PR.
+
+> **Note (Windows):** `clang-tidy` is not currently driven by our scripts on
+> Windows. The MSBuild CMake generator doesn't emit
+> `compile_commands.json`, which `clang-tidy` requires. The Ninja generator
+> does, so manual invocation is possible. `clang-format` similarly needs to be
+> installed and run manually on Windows, pointing at the root `.clang-format`.
+
+### Install
+
+**macOS:**
+
+```bash
+brew install llvm
+```
+
+This installs `clang-format`, `clang-tidy`, and `run-clang-tidy`. Homebrew may
+ask you to add `/opt/homebrew/opt/llvm/bin` (Apple Silicon) or
+`/usr/local/opt/llvm/bin` (Intel) to your `PATH`.
+
+**Linux (Ubuntu/Debian):**
+
+```bash
+sudo apt-get install clang-format clang-tidy clang-tools
+```
+
+### Run `clang-tidy`
+
+1. Generate `compile_commands.json` and the protobuf headers via a release build:
+
+ ```bash
+ ./build.sh release
+ ```
+
+2. Run the wrapper, which uses the same file set, regex filters, and
+ `.clang-tidy` config as CI:
+
+ ```bash
+ ./scripts/clang-tidy.sh
+ ```
+
+The wrapper forwards extra arguments to `run-clang-tidy`:
+
+```bash
+./scripts/clang-tidy.sh -j 4 # Number of cores
+./scripts/clang-tidy.sh -checks='-*,misc-const-correctness' # Only specific checks
+./scripts/clang-tidy.sh -fix # Apply fixes
+```
+
+Output is captured to `clang-tidy.log` at the repo root, since the terminal
+buffer often can't hold all of it.
+
+### Run `clang-format`
+
+```bash
+./scripts/clang-format.sh
+```
+
+With no arguments, runs against every relevant file in the repository against
+the rules in `.clang-format`.
+
+```bash
+./scripts/clang-format.sh --fix # Rewrite files in place
+./scripts/clang-format.sh src/room.cpp include/livekit/room.h # Check just these files
+./scripts/clang-format.sh --fix src/room.cpp # Fix just this file
+```
+
+Output is captured to `clang-format.log` at the repo root.
+
+---
+
+## Pre-commit hook
+
+A simple pre-commit hook that auto-formats staged C/C++ files using the
+project's `.clang-format` rules:
+
+```bash
+./scripts/install-pre-commit.sh
+```
+
+This installs `.git/hooks/pre-commit`. Re-run after `git clone` on a fresh
+checkout.
+
+---
+
+## Memory checks (valgrind)
+
+Run `valgrind` against the integration or stress test binaries to check for
+memory leaks and other issues:
+
+```bash
+valgrind --leak-check=full ./build-debug/bin/livekit_integration_tests
+valgrind --leak-check=full ./build-debug/bin/livekit_stress_tests
+```
+
+`valgrind` is Linux-only. On macOS, use `leaks` or Instruments instead.
+
+---
+
+## API documentation (Doxygen)
+
+API reference is generated from headers using Doxygen. To rebuild locally:
+
+```bash
+./scripts/generate-docs.sh
+```
+
+Output lands under `docs/doxygen/html/`. The deployed reference is at
+[docs.livekit.io/reference/client-sdk-cpp/](https://docs.livekit.io/reference/client-sdk-cpp/).
+
+To view the generated documentation locally, open `docs/doxygen/html/index.html` in your browser.
+
+For details on the Doxygen configuration and CI pipeline, see the
+[doxygen/](https://github.com/livekit/client-sdk-cpp/tree/main/docs/doxygen) folder.
+
+---
+
+## Development tips
+
+### Bump the pinned Rust submodule
+
+```bash
+cd client-sdk-cpp
+git fetch origin
+git switch -c try-rust-main origin/main
+
+# Sync submodule URLs and check out what origin/main pins (recursively):
+git submodule sync --recursive
+git submodule update --init --recursive --checkout
+
+# If the nested submodule under yuv-sys didn't materialize, force it:
+git -C client-sdk-rust/yuv-sys submodule sync --recursive
+git -C client-sdk-rust/yuv-sys submodule update --init --recursive --checkout
+
+# Sanity check:
+git submodule status --recursive
+```
+
+### If `yuv-sys` fails to build
+
+```bash
+cargo clean -p yuv-sys
+cargo build -p yuv-sys -vv
+```
+
+### Full clean (Rust + C++ build folders)
+
+To delete all build artifacts from both Rust and C++ folders, plus the
+local-install folder:
+
+```bash
+./build.sh clean-all
+```
diff --git a/docs/tracing.md b/docs/tracing.md
new file mode 100644
index 00000000..f74fa75c
--- /dev/null
+++ b/docs/tracing.md
@@ -0,0 +1,35 @@
+# Tracing
+
+The SDK includes built-in support for [Chromium tracing](https://www.chromium.org/developers/how-tos/trace-event-profiling-tool/),
+allowing you to capture detailed performance traces for debugging and
+optimization.
+
+## Basic usage
+
+```cpp
+#include
+
+// Start tracing to a file
+livekit::startTracing("trace.json");
+
+// ... run your application ...
+
+// Stop tracing and flush to file
+livekit::stopTracing();
+```
+
+## Filtering by category
+
+You can optionally filter which categories to trace:
+
+```cpp
+// Trace only specific categories (supports wildcards)
+livekit::startTracing("trace.json", {"livekit.*", "webrtc.*"});
+```
+
+## Viewing traces
+
+Open the generated trace file in one of these viewers:
+
+- **Chrome**: navigate to `chrome://tracing` and click "Load" to open the trace file.
+- **Perfetto**: open [ui.perfetto.dev](https://ui.perfetto.dev) and drag-drop your trace file.
diff --git a/include/livekit/audio_frame.h b/include/livekit/audio_frame.h
index 80678903..cac5765f 100644
--- a/include/livekit/audio_frame.h
+++ b/include/livekit/audio_frame.h
@@ -78,34 +78,6 @@ class LIVEKIT_API AudioFrame {
/// A human-readable description.
std::string toString() const;
- /// @deprecated Use totalSamples() instead.
- [[deprecated("AudioFrame::total_samples is deprecated; use AudioFrame::totalSamples instead")]]
- std::size_t total_samples() const noexcept { // NOLINT(readability-identifier-naming)
- return totalSamples();
- }
-
- /// @deprecated Use sampleRate() instead.
- [[deprecated("AudioFrame::sample_rate is deprecated; use AudioFrame::sampleRate instead")]]
- int sample_rate() const noexcept { // NOLINT(readability-identifier-naming)
- return sampleRate();
- }
-
- /// @deprecated Use numChannels() instead.
- [[deprecated("AudioFrame::num_channels is deprecated; use AudioFrame::numChannels instead")]]
- int num_channels() const noexcept { // NOLINT(readability-identifier-naming)
- return numChannels();
- }
-
- /// @deprecated Use samplesPerChannel() instead.
- [[deprecated("AudioFrame::samples_per_channel is deprecated; use AudioFrame::samplesPerChannel instead")]]
- int samples_per_channel() const noexcept { // NOLINT(readability-identifier-naming)
- return samplesPerChannel();
- }
-
- /// @deprecated Use toString() instead.
- [[deprecated("AudioFrame::to_string is deprecated; use AudioFrame::toString instead")]]
- std::string to_string() const; // NOLINT(readability-identifier-naming)
-
protected:
// Build a proto AudioFrameBufferInfo pointing at this frame’s data.
// Used internally by AudioSource.
diff --git a/include/livekit/audio_source.h b/include/livekit/audio_source.h
index 5c149dd0..249907c5 100644
--- a/include/livekit/audio_source.h
+++ b/include/livekit/audio_source.h
@@ -82,24 +82,6 @@ class LIVEKIT_API AudioSource {
/// Underlying FFI handle ID used in FFI requests.
std::uint64_t ffiHandleId() const noexcept { return static_cast(handle_.get()); }
- /// @deprecated Use sampleRate() instead.
- [[deprecated("AudioSource::sample_rate is deprecated; use AudioSource::sampleRate instead")]]
- int sample_rate() const noexcept { // NOLINT(readability-identifier-naming)
- return sampleRate();
- }
-
- /// @deprecated Use numChannels() instead.
- [[deprecated("AudioSource::num_channels is deprecated; use AudioSource::numChannels instead")]]
- int num_channels() const noexcept { // NOLINT(readability-identifier-naming)
- return numChannels();
- }
-
- /// @deprecated Use ffiHandleId() instead.
- [[deprecated("AudioSource::ffi_handle_id is deprecated; use AudioSource::ffiHandleId instead")]]
- std::uint64_t ffi_handle_id() const noexcept { // NOLINT(readability-identifier-naming)
- return ffiHandleId();
- }
-
/// Current duration of queued audio (in seconds).
double queuedDuration() const noexcept;
diff --git a/include/livekit/e2ee.h b/include/livekit/e2ee.h
index 4bc81a26..1ba74534 100644
--- a/include/livekit/e2ee.h
+++ b/include/livekit/e2ee.h
@@ -203,10 +203,17 @@ class LIVEKIT_API E2EEManager {
/// will result in undecodable media (black video / silent audio).
void setEnabled(bool enabled);
- /// Returns the key provider if E2EE was configured for the room; otherwise
- /// nullptr.
- KeyProvider* keyProvider();
- const KeyProvider* keyProvider() const;
+ /// Returns a weak reference to the key provider if E2EE was configured for
+ /// the room; otherwise an expired weak_ptr.
+ ///
+ /// The KeyProvider is owned by this E2EEManager (which is in turn owned by
+ /// Room). Callers must lock() the returned weak_ptr before use and must not
+ /// retain the resulting shared_ptr beyond the lifetime of the Room.
+ ///
+ /// @return A weak_ptr to the KeyProvider, or an expired weak_ptr if E2EE was
+ /// not configured.
+ std::weak_ptr keyProvider();
+ std::weak_ptr keyProvider() const;
/// Retrieves the current list of frame cryptors from the underlying runtime.
std::vector frameCryptors() const;
@@ -220,7 +227,9 @@ class LIVEKIT_API E2EEManager {
std::uint64_t room_handle_{0};
bool enabled_{false};
E2EEOptions options_;
- KeyProvider key_provider_;
+ /// The key provider is owned by the E2EEManager and is not shared with other objects.
+ /// It is a shared_ptr just to utilize the weak_ptr interface for the keyProvider() accessor.
+ std::shared_ptr key_provider_;
};
} // namespace livekit
diff --git a/include/livekit/livekit.h b/include/livekit/livekit.h
index d5ea02f5..e02ce67e 100644
--- a/include/livekit/livekit.h
+++ b/include/livekit/livekit.h
@@ -44,14 +44,6 @@
/// @brief Public API for the LiveKit C++ Client SDK.
namespace livekit {
-/// The log sink to use for SDK messages.
-enum class LogSink {
- /// Log messages to the console.
- kConsole = 0,
- /// Log messages to a callback function.
- kCallback = 1,
-};
-
/// Initialize the LiveKit SDK.
///
/// This **must be the first LiveKit API called** in the process.
@@ -59,10 +51,9 @@ enum class LogSink {
///
/// @param level Minimum log level for SDK messages (default: Info).
/// Use setLogLevel() to change at runtime.
-/// @param log_sink The log sink to use for SDK messages (default: Console).
/// @returns true if initialization happened on this call, false if it was
/// already initialized.
-LIVEKIT_API bool initialize(const LogLevel& level = LogLevel::Info, const LogSink& log_sink = LogSink::kConsole);
+LIVEKIT_API bool initialize(const LogLevel& level = LogLevel::Info);
/// Shut down the LiveKit SDK.
///
diff --git a/include/livekit/local_audio_track.h b/include/livekit/local_audio_track.h
index 827ba4e7..2797fcde 100644
--- a/include/livekit/local_audio_track.h
+++ b/include/livekit/local_audio_track.h
@@ -42,7 +42,9 @@ class AudioSource;
///
/// auto source = AudioSource::create(...);
/// auto track = LocalAudioTrack::createLocalAudioTrack("mic", source);
-/// room->localParticipant()->publishTrack(track);
+/// if (auto lp = room->localParticipant().lock()) {
+/// lp->publishTrack(track);
+/// }
///
/// Muting a local audio track stops transmitting audio to the room, but
/// the underlying source may continue capturing depending on platform
@@ -77,12 +79,6 @@ class LIVEKIT_API LocalAudioTrack : public Track {
/// including its SID and name. Useful for debugging and logging.
std::string toString() const;
- /// @deprecated Use toString() instead.
- // NOLINTBEGIN(readability-identifier-naming)
- [[deprecated("LocalAudioTrack::to_string is deprecated; use LocalAudioTrack::toString instead")]]
- std::string to_string() const;
- // NOLINTEND(readability-identifier-naming)
-
/// Returns the publication that owns this track, or nullptr if the track is
/// not published.
std::shared_ptr publication() const noexcept { return local_publication_; }
diff --git a/include/livekit/local_data_track.h b/include/livekit/local_data_track.h
index 80d57dac..18c2eb77 100644
--- a/include/livekit/local_data_track.h
+++ b/include/livekit/local_data_track.h
@@ -45,7 +45,8 @@ class OwnedLocalDataTrack;
///
/// Typical usage:
///
-/// auto lp = room->localParticipant();
+/// auto lp = room->localParticipant().lock();
+/// if (!lp) return; // room not connected or already torn down
/// auto result = lp->publishDataTrack("sensor-data");
/// if (result) {
/// auto dt = result.value();
diff --git a/include/livekit/local_video_track.h b/include/livekit/local_video_track.h
index 7b8da0ef..c89893de 100644
--- a/include/livekit/local_video_track.h
+++ b/include/livekit/local_video_track.h
@@ -41,7 +41,9 @@ class VideoSource;
///
/// auto source = std::make_shared(1280, 720);
/// auto track = LocalVideoTrack::createLocalVideoTrack("cam", source);
-/// room->localParticipant()->publishTrack(track);
+/// if (auto lp = room->localParticipant().lock()) {
+/// lp->publishTrack(track);
+/// }
/// // Capture frames on the video thread via `source`, not via the track.
///
/// Muting a local video track stops transmitting video to the room, but
@@ -77,12 +79,6 @@ class LIVEKIT_API LocalVideoTrack : public Track {
/// including its SID and name. Useful for debugging and logging.
std::string toString() const;
- /// @deprecated Use toString() instead.
- // NOLINTBEGIN(readability-identifier-naming)
- [[deprecated("LocalVideoTrack::to_string is deprecated; use LocalVideoTrack::toString instead")]]
- std::string to_string() const;
- // NOLINTEND(readability-identifier-naming)
-
/// Returns the publication that owns this track, or nullptr if the track is
/// not published.
std::shared_ptr publication() const noexcept { return local_publication_; }
diff --git a/include/livekit/participant.h b/include/livekit/participant.h
index 2b5857db..eeecba52 100644
--- a/include/livekit/participant.h
+++ b/include/livekit/participant.h
@@ -56,58 +56,6 @@ class LIVEKIT_API Participant {
uintptr_t ffiHandleId() const noexcept { return handle_.get(); }
- // ---------------------------------------------------------------------------
- // Deprecated public mutators
- // ---------------------------------------------------------------------------
-
- // NOLINTBEGIN(readability-identifier-naming)
-
- /// @deprecated Use setName() instead.
- [[deprecated("Participant::set_name is deprecated; use LocalParticipant::setName instead")]]
- void set_name(std::string name) noexcept {
- name_ = std::move(name);
- }
-
- /// @deprecated Use setMetadata() instead.
- [[deprecated("Participant::set_metadata is deprecated; use LocalParticipant::setMetadata instead")]]
- void set_metadata(std::string metadata) noexcept {
- metadata_ = std::move(metadata);
- }
-
- /// @deprecated Use setAttributes() instead.
- [[deprecated("Participant::set_attributes is deprecated; use LocalParticipant::setAttributes instead")]]
- void set_attributes(std::unordered_map attrs) noexcept {
- attributes_ = std::move(attrs);
- }
-
- /// @deprecated Use setAttribute() instead.
- [[deprecated("Participant::set_attribute is deprecated; use LocalParticipant::setAttributes instead")]]
- void set_attribute(const std::string& key, const std::string& value) {
- attributes_[key] = value;
- }
-
- /// @deprecated Use removeAttribute() instead.
- [[deprecated("Participant::remove_attribute is deprecated; use LocalParticipant::setAttributes instead")]]
- void remove_attribute(const std::string& key) {
- attributes_.erase(key);
- }
-
- /// @deprecated Kind is server-determined and not user-settable; this mutator will be removed.
- [[deprecated("Participant::set_kind is deprecated; Kind is server-determined and not user-settable")]]
- void set_kind(ParticipantKind kind) noexcept {
- kind_ = kind;
- }
-
- /// @deprecated DisconnectReason is server-determined and not user-settable; this mutator will be removed.
- [[deprecated(
- "Participant::set_disconnect_reason is deprecated; DisconnectReason is server-determined and not "
- "user-settable")]]
- void set_disconnect_reason(DisconnectReason reason) noexcept {
- reason_ = reason;
- }
-
- // NOLINTEND(readability-identifier-naming)
-
protected:
virtual std::shared_ptr findTrackPublication(const std::string& sid) const = 0;
diff --git a/include/livekit/remote_audio_track.h b/include/livekit/remote_audio_track.h
index 837e0262..cfbee358 100644
--- a/include/livekit/remote_audio_track.h
+++ b/include/livekit/remote_audio_track.h
@@ -50,12 +50,6 @@ class LIVEKIT_API RemoteAudioTrack : public Track {
/// Returns a concise, human-readable string summarizing the track,
/// including its SID and name. Useful for debugging and logging.
std::string toString() const;
-
- /// @deprecated Use toString() instead.
- // NOLINTBEGIN(readability-identifier-naming)
- [[deprecated("RemoteAudioTrack::to_string is deprecated; use RemoteAudioTrack::toString instead")]]
- std::string to_string() const;
- // NOLINTEND(readability-identifier-naming)
};
} // namespace livekit
\ No newline at end of file
diff --git a/include/livekit/remote_participant.h b/include/livekit/remote_participant.h
index 1addd141..c050c0e3 100644
--- a/include/livekit/remote_participant.h
+++ b/include/livekit/remote_participant.h
@@ -44,12 +44,6 @@ class LIVEKIT_API RemoteParticipant : public Participant {
std::string toString() const;
- /// @deprecated Use toString() instead.
- // NOLINTBEGIN(readability-identifier-naming)
- [[deprecated("RemoteParticipant::to_string is deprecated; use RemoteParticipant::toString instead")]]
- std::string to_string() const;
- // NOLINTEND(readability-identifier-naming)
-
protected:
/// Called by Room events like kTrackMuted. This is internal plumbing and not
/// intended to be called directly by SDK users.
diff --git a/include/livekit/remote_video_track.h b/include/livekit/remote_video_track.h
index 0f2054ac..f3cf5dfc 100644
--- a/include/livekit/remote_video_track.h
+++ b/include/livekit/remote_video_track.h
@@ -50,12 +50,6 @@ class LIVEKIT_API RemoteVideoTrack : public Track {
/// Returns a concise, human-readable string summarizing the track,
/// including its SID and name. Useful for debugging and logging.
std::string toString() const;
-
- /// @deprecated Use toString() instead.
- // NOLINTBEGIN(readability-identifier-naming)
- [[deprecated("RemoteVideoTrack::to_string is deprecated; use RemoteVideoTrack::toString instead")]]
- std::string to_string() const;
- // NOLINTEND(readability-identifier-naming)
};
} // namespace livekit
\ No newline at end of file
diff --git a/include/livekit/result.h b/include/livekit/result.h
index 6155fe92..0c3f5a69 100644
--- a/include/livekit/result.h
+++ b/include/livekit/result.h
@@ -58,14 +58,6 @@ class [[nodiscard]] Result {
/// Allows `if (result)` style success checks.
explicit operator bool() const noexcept { return ok(); }
- /// @deprecated Use hasError() instead.
- // NOLINTBEGIN(readability-identifier-naming)
- [[deprecated("Result::has_error is deprecated; use Result::hasError instead")]]
- bool has_error() const noexcept {
- return hasError();
- }
- // NOLINTEND(readability-identifier-naming)
-
/// Access the success value.
///
/// @throws std::logic_error if `ok() == false`.
diff --git a/include/livekit/room.h b/include/livekit/room.h
index 5c9d65b4..74ea94d6 100644
--- a/include/livekit/room.h
+++ b/include/livekit/room.h
@@ -16,7 +16,6 @@
#pragma once
-#include
#include
#include
#include
@@ -110,13 +109,15 @@ class LIVEKIT_API Room {
/// MyDelegate del;
/// Room room;
/// room.setDelegate(&del);
- ///
- /// @param delegate The RoomDelegate implementation to receive room lifecycle callbacks.
void setDelegate(RoomDelegate* delegate);
/// Connect to a LiveKit room using the given URL and token, applying the
/// supplied connection options.
///
+ /// @param url WebSocket URL of the LiveKit server.
+ /// @param token Access token for authentication.
+ /// @param options Connection options controlling auto-subscribe,
+ /// dynacast, E2EE, and WebRTC configuration.
/// Behavior:
/// - Registers an FFI event listener *before* sending the connect request.
/// - Sends a proto::FfiRequest::Connect with the URL, token,
@@ -128,19 +129,8 @@ class LIVEKIT_API Room {
/// RoomOptions defaults auto_subscribe = true.
/// Without auto_subscribe enabled, remote tracks will NOT be subscribed
/// automatically, and no remote audio/video will ever arrive.
- ///
- /// @param url WebSocket URL of the LiveKit server.
- /// @param token Access token for authentication.
- /// @param options Connection options controlling auto-subscribe,
- /// dynacast, E2EE, and WebRTC configuration.
bool connect(const std::string& url, const std::string& token, const RoomOptions& options);
- /// @deprecated Use connect() instead.
- // NOLINTBEGIN(readability-identifier-naming)
- [[deprecated("Room::Connect is deprecated; use Room::connect instead")]]
- bool Connect(const std::string& url, const std::string& token, const RoomOptions& options);
- // NOLINTEND(readability-identifier-naming)
-
/// Disconnect from the room.
///
/// This method attempts a best-effort graceful disconnect of the room. If the room was connected prior, after @ref
@@ -168,33 +158,40 @@ class LIVEKIT_API Room {
/// - creation timestamp
RoomInfoData roomInfo() const;
- /// @deprecated Use roomInfo() instead.
- // NOLINTBEGIN(readability-identifier-naming)
- [[deprecated("Room::room_info is deprecated; use Room::roomInfo instead")]]
- RoomInfoData room_info() const;
- // NOLINTEND(readability-identifier-naming)
-
/// Get the local participant.
///
/// This object represents the current user, including:
/// - published tracks (audio/video/screen)
/// - identity, SID, metadata
/// - publishing/unpublishing operations
- /// @return Non-null pointer after successful connect().
- LocalParticipant* localParticipant() const;
+ ///
+ /// The returned handle is non-owning. Call @c lock() to obtain a usable
+ /// @c weak_ptr; the result is empty (`lock() == nullptr`) before connect,
+ /// after room end-of-stream teardown, or once the room is destroyed. This
+ /// lets callers that cache the handle detect object lifetime instead of holding a
+ /// dangling pointer.
+ ///
+ /// @return Weak handle to the local participant.
+ std::weak_ptr localParticipant() const;
/// Look up a remote participant by identity.
///
- /// @param identity The participant’s identity string (not SID)
- /// @return Pointer to RemoteParticipant if present, otherwise nullptr.
- /// RemoteParticipant contains:
+ /// @param identity The participant’s identity string (not SID).
+ /// @return Weak handle to the RemoteParticipant if present, otherwise an
+ /// empty handle (`lock() == nullptr`). The handle also becomes empty once
+ /// the participant disconnects, the room is torn down, or the room is
+ /// destroyed. RemoteParticipant contains:
/// - identity/name/metadata
/// - track publications
- /// - callbacks for track subscribed/unsubscribed, muted/unmuted
- RemoteParticipant* remoteParticipant(const std::string& identity) const;
+ /// - callbacks for track subscribed/unsubscribed, muted/unmuted
+ std::weak_ptr remoteParticipant(const std::string& identity) const;
/// Returns a snapshot of all current remote participants.
- std::vector> remoteParticipants() const;
+ ///
+ /// @return Vector of weak handles to the current remote participants. Each
+ /// handle can be promoted with @c lock(); a handle becomes empty once the
+ /// corresponding participant disconnects or the room is torn down.
+ std::vector> remoteParticipants() const;
/// Returns the current connection state of the room.
ConnectionState connectionState() const;
@@ -249,41 +246,33 @@ class LIVEKIT_API Room {
/// - The ByteStreamReader remains valid as long as the shared_ptr is held,
/// preventing lifetime-related crashes when reading asynchronously.
///
- /// @param topic The topic to register the byte stream handler for.
- /// @param handler The ByteStreamHandler to invoke when a byte stream is received.
/// @throws std::runtime_error if a handler is already registered for the topic.
void registerByteStreamHandler(const std::string& topic, ByteStreamHandler handler);
/// Unregister the byte stream handler for the given topic.
///
/// If no handler exists for the topic, this function is a no-op.
- /// @param topic The topic to unregister the byte stream handler for.
void unregisterByteStreamHandler(const std::string& topic);
- /// Returns the room's E2EE manager, or nullptr if E2EE was not enabled at
- /// connect time.
+ /// Returns the room's E2EE manager as a weak handle, or an empty handle if
+ /// E2EE was not enabled at connect time.
///
/// Notes:
/// - The manager is created after a successful connect().
- /// - If E2EE was not configured in RoomOptions, this will return nullptr.
- E2EEManager* e2eeManager() const;
+ /// - If E2EE was not configured in RoomOptions, @c lock() returns nullptr.
+ /// - The handle also becomes empty once the room is torn down or destroyed.
+ ///
+ /// @return Weak handle to the E2EE manager.
+ std::weak_ptr e2eeManager() const;
// ---------------------------------------------------------------
// Frame callbacks
// ---------------------------------------------------------------
- /// @brief Sets the audio frame callback via SubscriptionThreadDispatcher.
- void setOnAudioFrameCallback(const std::string& participant_identity, TrackSource source, AudioFrameCallback callback,
- const AudioStream::Options& opts = {});
-
/// @brief Sets the audio frame callback via SubscriptionThreadDispatcher.
void setOnAudioFrameCallback(const std::string& participant_identity, const std::string& track_name,
AudioFrameCallback callback, const AudioStream::Options& opts = {});
- /// @brief Sets the video frame callback via SubscriptionThreadDispatcher.
- void setOnVideoFrameCallback(const std::string& participant_identity, TrackSource source, VideoFrameCallback callback,
- const VideoStream::Options& opts = {});
-
/// @brief Sets the video frame callback via SubscriptionThreadDispatcher.
void setOnVideoFrameCallback(const std::string& participant_identity, const std::string& track_name,
VideoFrameCallback callback, const VideoStream::Options& opts = {});
@@ -293,14 +282,9 @@ class LIVEKIT_API Room {
void setOnVideoFrameEventCallback(const std::string& participant_identity, const std::string& track_name,
VideoFrameEventCallback callback, const VideoStream::Options& opts = {});
- /// @brief Clears the audio frame callback via SubscriptionThreadDispatcher.
- void clearOnAudioFrameCallback(const std::string& participant_identity, TrackSource source);
/// @brief Clears the audio frame callback via SubscriptionThreadDispatcher.
void clearOnAudioFrameCallback(const std::string& participant_identity, const std::string& track_name);
- /// @brief Clears the video frame callback via SubscriptionThreadDispatcher.
- void clearOnVideoFrameCallback(const std::string& participant_identity, TrackSource source);
-
/// @brief Clears the video frame callback via SubscriptionThreadDispatcher.
void clearOnVideoFrameCallback(const std::string& participant_identity, const std::string& track_name);
@@ -319,15 +303,18 @@ class LIVEKIT_API Room {
RoomDelegate* delegate_ = nullptr; // Not owned
RoomInfoData room_info_;
std::shared_ptr room_handle_;
- std::unique_ptr local_participant_;
+ /// The local participant is owned by the room and is not shared with other objects.
+ /// It is a shared_ptr just to utilize the weak_ptr interface for the localParticipant() accessor.
+ std::shared_ptr local_participant_;
std::unordered_map> remote_participants_;
// Data stream
std::unordered_map text_stream_handlers_;
std::unordered_map byte_stream_handlers_;
std::unordered_map> text_stream_readers_;
std::unordered_map> byte_stream_readers_;
- // E2EE
- std::unique_ptr e2ee_manager_;
+ // The E2EE manager is owned by the room and is not shared with other objects.
+ // It is a shared_ptr just to utilize the weak_ptr interface for the e2eeManager() accessor.
+ std::shared_ptr e2ee_manager_;
std::shared_ptr subscription_thread_dispatcher_;
// FfiClient listener ID (0 means no listener registered)
diff --git a/include/livekit/subscription_thread_dispatcher.h b/include/livekit/subscription_thread_dispatcher.h
index f62b78ce..73c86684 100644
--- a/include/livekit/subscription_thread_dispatcher.h
+++ b/include/livekit/subscription_thread_dispatcher.h
@@ -39,11 +39,11 @@ class Track;
class VideoFrame;
/// Callback type for incoming audio frames.
-/// Invoked on a dedicated reader thread per (participant, source) pair.
+/// Invoked on a dedicated reader thread per (participant, track_name) pair.
using AudioFrameCallback = std::function;
/// Callback type for incoming video frames.
-/// Invoked on a dedicated reader thread per (participant, source) pair.
+/// Invoked on a dedicated reader thread per (participant, track_name) pair.
using VideoFrameCallback = std::function