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/README.md b/README.md
index 665f0042..f8f5c285 100644
--- a/README.md
+++ b/README.md
@@ -621,11 +621,9 @@ lk token create \
--grant '{"canPublish":true,"canSubscribe":true,"canPublishData":true}'
```
-## Deprecation
-
-- 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.
+# 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 [PR #143](https://github.com/livekit/client-sdk-cpp/pull/143) for the source code changes.
diff --git a/benchmarks/data_track_throughput/consumer.cpp b/benchmarks/data_track_throughput/consumer.cpp
index d3fde8d2..2d031541 100644
--- a/benchmarks/data_track_throughput/consumer.cpp
+++ b/benchmarks/data_track_throughput/consumer.cpp
@@ -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..b0732cb6 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();
+ }
}
}
@@ -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/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/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 ac80fb4d..81664de7 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)
-
// Accessors
/// Retrieve static metadata about the room.
@@ -152,33 +142,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;
@@ -233,41 +230,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 = {});
@@ -277,14 +266,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);
@@ -303,15 +287,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;
/// Callback type for incoming video frame events.
@@ -68,7 +68,7 @@ using DataFrameCallbackId = std::uint64_t;
/// registration requests here, and then calls @ref handleTrackSubscribed and
/// @ref handleTrackUnsubscribed as room events arrive.
///
-/// For each registered `(participant identity, TrackSource)` pair, this class
+/// For each registered `(participant identity, track name)` pair, this class
/// may create a dedicated @ref AudioStream or @ref VideoStream and a matching
/// reader thread. That thread blocks on stream reads and invokes the
/// registered callback with decoded frames.
@@ -89,20 +89,6 @@ class LIVEKIT_API SubscriptionThreadDispatcher {
/// Stops all active readers and clears all registered callbacks.
~SubscriptionThreadDispatcher();
- /// Register or replace an audio frame callback for a remote subscription.
- ///
- /// The callback is keyed by remote participant identity plus @p source.
- /// If the matching remote audio track is already subscribed, @ref Room may
- /// immediately call @ref handleTrackSubscribed to start a reader.
- ///
- /// @param participant_identity Identity of the remote participant.
- /// @param source Track source to match.
- /// @param callback Function invoked for each decoded audio frame.
- /// @param opts Options used when creating the backing
- /// @ref AudioStream.
- void setOnAudioFrameCallback(const std::string& participant_identity, TrackSource source, AudioFrameCallback callback,
- const AudioStream::Options& opts = {});
-
/// Register or replace an audio frame callback for a remote subscription.
///
/// The callback is keyed by remote participant identity plus @p track_name.
@@ -117,20 +103,6 @@ class LIVEKIT_API SubscriptionThreadDispatcher {
void setOnAudioFrameCallback(const std::string& participant_identity, const std::string& track_name,
AudioFrameCallback callback, const AudioStream::Options& opts = {});
- /// Register or replace a video frame callback for a remote subscription.
- ///
- /// The callback is keyed by remote participant identity plus @p source.
- /// If the matching remote video track is already subscribed, @ref Room may
- /// immediately call @ref handleTrackSubscribed to start a reader.
- ///
- /// @param participant_identity Identity of the remote participant.
- /// @param source Track source to match.
- /// @param callback Function invoked for each decoded video frame.
- /// @param opts Options used when creating the backing
- /// @ref VideoStream.
- void setOnVideoFrameCallback(const std::string& participant_identity, TrackSource source, VideoFrameCallback callback,
- const VideoStream::Options& opts = {});
-
/// Register or replace a video frame callback for a remote subscription.
///
/// The callback is keyed by remote participant identity plus @p track_name.
@@ -161,15 +133,6 @@ class LIVEKIT_API SubscriptionThreadDispatcher {
void setOnVideoFrameEventCallback(const std::string& participant_identity, const std::string& track_name,
VideoFrameEventCallback callback, const VideoStream::Options& opts = {});
- /// Remove an audio callback registration and stop any active reader.
- ///
- /// If an audio reader thread is active for the given key, its stream is
- /// closed and the thread is joined before this call returns.
- ///
- /// @param participant_identity Identity of the remote participant.
- /// @param source Track source to clear.
- void clearOnAudioFrameCallback(const std::string& participant_identity, TrackSource source);
-
/// Remove an audio callback registration and stop any active reader.
///
/// If an audio reader thread is active for the given key, its stream is
@@ -179,15 +142,6 @@ class LIVEKIT_API SubscriptionThreadDispatcher {
/// @param track_name Track name to clear.
void clearOnAudioFrameCallback(const std::string& participant_identity, const std::string& track_name);
- /// Remove a video callback registration and stop any active reader.
- ///
- /// If a video reader thread is active for the given key, its stream is
- /// closed and the thread is joined before this call returns.
- ///
- /// @param participant_identity Identity of the remote participant.
- /// @param source Track source to clear.
- void clearOnVideoFrameCallback(const std::string& participant_identity, TrackSource source);
-
/// Remove a video callback registration and stop any active reader.
///
/// If a video reader thread is active for the given key, its stream is
@@ -202,21 +156,20 @@ class LIVEKIT_API SubscriptionThreadDispatcher {
/// @ref Room calls this after it has processed a track-subscription event and
/// updated its publication state. If a matching callback registration exists,
/// the dispatcher creates the appropriate stream type and launches a reader
- /// thread for the `(participant, source)` key.
+ /// thread for the `(participant, track_name)` key.
///
/// If no matching callback is registered, this is a no-op.
///
/// @param participant_identity Identity of the remote participant.
- /// @param source Track source associated with the subscription.
/// @param track_name Track name associated with the subscription.
/// @param track Subscribed remote track to read from.
- void handleTrackSubscribed(const std::string& participant_identity, TrackSource source, const std::string& track_name,
+ void handleTrackSubscribed(const std::string& participant_identity, const std::string& track_name,
const std::shared_ptr