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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/builds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:
- src/**
- include/**
- examples/**
- bridge/**
Copy link
Collaborator

Choose a reason for hiding this comment

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

the bridge/ folder code only be internal ?

if so, I think we should put the code under src folder
the structure should be clean like
src/ : contains all the internal source code
tests/ contains all the test code
include/livekit : contain all the public interface

as we are adding bridge, if it is only internal to src, lets put it under src/internal/bridge

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sounds good -- i will make this a single git mv commit at the end so comments are easier to follow!

- client-sdk-rust/**
- CMakeLists.txt
- build.sh
Expand All @@ -20,6 +21,7 @@ on:
- src/**
- include/**
- examples/**
- bridge/**
- client-sdk-rust/**
- CMakeLists.txt
- build.sh
Expand Down
8 changes: 4 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ set(FFI_PROTO_FILES
${FFI_PROTO_DIR}/e2ee.proto
${FFI_PROTO_DIR}/stats.proto
${FFI_PROTO_DIR}/data_stream.proto
${FFI_PROTO_DIR}/data_track.proto
${FFI_PROTO_DIR}/rpc.proto
${FFI_PROTO_DIR}/track_publication.proto
)
Expand Down Expand Up @@ -281,13 +282,16 @@ add_library(livekit SHARED
src/audio_source.cpp
src/audio_stream.cpp
src/data_stream.cpp
src/data_track_subscription.cpp
src/e2ee.cpp
src/ffi_handle.cpp
src/ffi_client.cpp
src/ffi_client.h
src/livekit.cpp
src/local_audio_track.cpp
src/local_data_track.cpp
src/remote_audio_track.cpp
src/remote_data_track.cpp
src/room.cpp
src/room_proto_converter.cpp
src/room_proto_converter.h
Expand Down Expand Up @@ -631,10 +635,6 @@ install(FILES
# Build the LiveKit C++ bridge before examples (human_robot depends on it)
add_subdirectory(bridge)

# ---- Examples ----
# add_subdirectory(examples)


if(LIVEKIT_BUILD_EXAMPLES)
add_subdirectory(examples)
endif()
Expand Down
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -341,5 +341,26 @@ CPP SDK is using clang C++ format
brew install clang-format
```

# Running locally
1. Install the livekit-server
https://docs.livekit.io/transport/self-hosting/local/

Start the livekit-server with data tracks enabled:
```bash
LIVEKIT_CONFIG="enable_data_tracks: true" livekit-server --dev
```

```bash
# generate tokens, do for all participants
lk token create \
--api-key devkey \
--api-secret secret \
-i robot \
--join \
--valid-for 99999h \
--room robo_room \
--grant '{"canPublish":true,"canSubscribe":true,"canPublishData":true}'
```

<!--BEGIN_REPO_NAV-->
<!--END_REPO_NAV-->
1 change: 1 addition & 0 deletions bridge/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_library(livekit_bridge SHARED
src/livekit_bridge.cpp
src/bridge_audio_track.cpp
src/bridge_data_track.cpp
src/bridge_video_track.cpp
src/bridge_room_delegate.cpp
src/bridge_room_delegate.h
Expand Down
43 changes: 34 additions & 9 deletions bridge/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,10 +206,7 @@ The human will print periodic summaries like:

## Testing

The bridge includes a unit test suite built with [Google Test](https://github.com/google/googletest). Tests cover
1. `CallbackKey` hashing/equality,
2. `BridgeAudioTrack`/`BridgeVideoTrack` state management, and
3. `LiveKitBridge` pre-connection behaviour (callback registration, error handling).
The bridge includes a unit test suite built with [Google Test](https://github.com/google/googletest).

### Building and running tests

Expand All @@ -222,18 +219,46 @@ Bridge tests are automatically included when you build with the `debug-tests` or
Then run them directly:

```bash
./build-debug/bin/livekit_bridge_tests
./build-debug/bin/livekit_bridge_*_tests
```

### Standalone bridge tests only
### Bridge Tests

If you want to build bridge tests independently (without the parent SDK tests), set `LIVEKIT_BRIDGE_BUILD_TESTS=ON`:
The bridge layer (`bridge/tests/`) has its own integration and stress tests
that exercise the full `LiveKitBridge` API over a real LiveKit server.
They use the **same environment variables** as the SDK tests above.

```bash
cmake --preset macos-debug -DLIVEKIT_BRIDGE_BUILD_TESTS=ON
cmake --build build-debug --target livekit_bridge_tests
# Run bridge tests via CTest
cd build-debug && ctest -L "bridge_integration" --output-on-failure
cd build-debug && ctest -L "bridge_stress" --output-on-failure

# Or run executables directly
./build-debug/bin/livekit_bridge_integration_tests
./build-debug/bin/livekit_bridge_stress_tests

# Run specific test suites
./build-debug/bin/livekit_bridge_integration_tests --gtest_filter="*AudioFrameRoundTrip*"
./build-debug/bin/livekit_bridge_stress_tests --gtest_filter="*HighThroughput*"
```

| Executable | Description |
|------------|-------------|
| `livekit_bridge_unit_tests` | Unit tests (no server required) |
| `livekit_bridge_integration_tests` | Audio & data round-trip, latency, connect/disconnect cycles |
| `livekit_bridge_stress_tests` | Sustained push, lifecycle, callback churn, multi-track concurrency |

#### Stress test suites

| Test file | Tests | What it exercises |
|-----------|-------|-------------------|
| `test_bridge_audio_stress` | SustainedAudioPush, ReleaseUnderActivePush, RapidConnectDisconnectWithCallback | Audio push at real-time pace, release/push race, abrupt teardown |
| `test_bridge_data_stress` | HighThroughput, LargePayloadStress, CallbackChurn | Data throughput, 64 KB frames, data callback register/clear churn |
| `test_bridge_lifecycle_stress` | DisconnectUnderLoad, TrackReleaseWhileReceiving, FullLifecycleSoak | Disconnect with active pushers+receivers, mid-stream track release, repeated full create→push→release→destroy |
| `test_bridge_callback_stress` | AudioCallbackChurn, MixedCallbackChurn, CallbackReplacement | Audio set/clear churn, mixed audio+data churn, rapid callback overwrite |
| `test_bridge_multi_track_stress` | ConcurrentMultiTrackPush, ConcurrentCreateRelease, FullDuplexMultiTrack | 4 tracks pushed concurrently, create/release cycles under contention, bidirectional audio+data |


## Limitations

The bridge is designed for simplicity and currently only supports limited audio and video features. It does not expose:
Expand Down
6 changes: 5 additions & 1 deletion bridge/include/livekit_bridge/bridge_audio_track.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2025 LiveKit
* 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.
Expand Down Expand Up @@ -55,6 +55,10 @@ class BridgeAudioTrackTest;
* one thread while another calls mute()/unmute()/release(), or to call
* pushFrame() concurrently from multiple threads.
*
* 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);
Expand Down
127 changes: 127 additions & 0 deletions bridge/include/livekit_bridge/bridge_data_track.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* 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.
*/

#pragma once

#include <cstdint>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <vector>

namespace livekit {
class LocalDataTrack;
class LocalParticipant;
} // namespace livekit

namespace livekit_bridge {

namespace test {
class BridgeDataTrackTest;
} // namespace test

/**
* Handle to a published local data track.
*
* Created via LiveKitBridge::createDataTrack(). 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.
*
* Unlike BridgeAudioTrack / BridgeVideoTrack, data tracks have no
* Source, Publication, mute(), or unmute(). They carry arbitrary binary
* frames via pushFrame().
*
* All public methods are thread-safe.
*
* Usage:
* auto dt = bridge.createDataTrack("sensor-data");
* dt->pushFrame({0x01, 0x02, 0x03});
* dt->release(); // unpublishes mid-session
*/
class BridgeDataTrack {
public:
~BridgeDataTrack();

BridgeDataTrack(const BridgeDataTrack &) = delete;
BridgeDataTrack &operator=(const BridgeDataTrack &) = delete;

/**
* Push a binary frame to all subscribers of this data track.
*
* @param payload Raw bytes to send.
* @param user_timestamp Optional application-defined timestamp.
* @return true if the frame was pushed, false if the track has been
* released or the push failed (e.g. back-pressure).
*/
bool pushFrame(const std::vector<std::uint8_t> &payload,
std::optional<std::uint64_t> user_timestamp = std::nullopt);

/**
* Push a binary frame from a raw pointer.
*
* @param data Pointer to raw bytes.
* @param size Number of bytes.
* @param user_timestamp Optional application-defined timestamp.
* @return true on success, false if released or push failed.
*/
bool pushFrame(const std::uint8_t *data, std::size_t size,
std::optional<std::uint64_t> user_timestamp = std::nullopt);

/// Track name as provided at creation.
const std::string &name() const noexcept { return name_; }

/// Whether the track is still published in the room.
bool isPublished() const;

/// Whether this track has been released / unpublished.
bool isReleased() const noexcept;

/**
* Explicitly unpublish the track and release underlying SDK resources.
*
* After this call, pushFrame() returns false. Called automatically by the
* destructor and by LiveKitBridge::disconnect(). Safe to call multiple
* times (idempotent).
*/
void release();

private:
friend class LiveKitBridge;
friend class test::BridgeDataTrackTest;

BridgeDataTrack(std::string name,
std::shared_ptr<livekit::LocalDataTrack> track,
livekit::LocalParticipant *participant);

/** Protects released_ and track_ for thread-safe access. */
mutable std::mutex mutex_;

/** Publisher-assigned track name (immutable after construction). */
std::string name_;

/** True after release() or disconnect(); prevents further pushFrame(). */
bool released_ = false;

/** Underlying SDK data track handle. Nulled on release(). */
std::shared_ptr<livekit::LocalDataTrack> track_;

/** Participant that published this track; used for unpublish. Not owned. */
livekit::LocalParticipant *participant_ = nullptr;
};

} // namespace livekit_bridge
6 changes: 5 additions & 1 deletion bridge/include/livekit_bridge/bridge_video_track.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2025 LiveKit
* 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.
Expand Down Expand Up @@ -55,6 +55,10 @@ class BridgeVideoTrackTest;
* one thread while another calls mute()/unmute()/release(), or to call
* pushFrame() concurrently from multiple threads.
*
* 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);
Expand Down
Loading
Loading