Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
7f4805f
DataTracks integration through FFI
stephen-derosa Mar 20, 2026
5828347
compiling with new data tracks work
stephen-derosa Mar 19, 2026
1fafccf
data tracks buffering is now handled on the rust side
stephen-derosa Mar 19, 2026
f58e859
data tracks buffering is now handled on the rust side
stephen-derosa Mar 19, 2026
df4f82b
replace cout/cerr with LK_LOG
stephen-derosa Mar 20, 2026
dd75aa3
no longer need BridgeDataTrack()
stephen-derosa Mar 20, 2026
9b2c49f
create*Track -> publish*Track()
stephen-derosa Mar 20, 2026
53fe0ec
dont add/remove stuff from bridge since we are targetting a branch wh…
stephen-derosa Mar 24, 2026
bd6008b
ActiveDataReader: subscriber mutex
stephen-derosa Mar 24, 2026
ccd91e8
DataTrackSubscription: read() sends request for latest message
stephen-derosa Mar 24, 2026
e413a18
move realsense-livekit out of this
stephen-derosa Mar 24, 2026
15b862c
no changes to bridge
stephen-derosa Mar 24, 2026
5a4fdf6
simple_status example
stephen-derosa Mar 24, 2026
d82fa18
multiple subscriptions
stephen-derosa Mar 24, 2026
0194984
hello_livekit example for a video track and data tracl
stephen-derosa Mar 25, 2026
52e00ff
move data track callback functionality to subscription_thread_dispatcher
stephen-derosa Mar 25, 2026
6b6abb8
hello_livekit: sender.cpp/receiver.cpp
stephen-derosa Mar 25, 2026
71a9c07
some data_tracks tests
stephen-derosa Mar 25, 2026
5d92bed
test_data_tracks.cpp
stephen-derosa Mar 25, 2026
a03f192
setOnAudio/VideoFrameCallback() change signature to use track name
stephen-derosa Mar 25, 2026
a9ff37c
setOnAudio/VideoFrameCallback() addition signature to use track name
stephen-derosa Mar 25, 2026
0974e57
testing for new API
stephen-derosa Mar 25, 2026
011b91a
data_track_subscription: on pushFrame() called when rust sends new fr…
stephen-derosa Mar 25, 2026
4f2d80a
DataTrack constructor operators for compiler efficiency. remove uneed…
stephen-derosa Mar 27, 2026
bf9a1b6
DataFrame::fromOwnedInfo to align with video/audio_stream.cpp
stephen-derosa Mar 27, 2026
ac90051
DataTrackSubscription: remove move constructor since it introduces ba…
stephen-derosa Mar 27, 2026
ddab010
result.h file for floating up specific errors provided by the FFI
stephen-derosa Mar 27, 2026
b5aea7b
BUG FIX: reset the frame after usage
stephen-derosa Mar 30, 2026
61cbd0c
lcaol_data_track: remove many overloaded tryPush() functions
stephen-derosa Mar 30, 2026
907d60c
subscribeDataTrackAsync -> subscribeDataTrack
stephen-derosa Mar 30, 2026
f187f9e
update to new proto
stephen-derosa Apr 1, 2026
5f912dd
update token gen script to run for all tokens needed
stephen-derosa Apr 1, 2026
78dacdd
Update client-sdk-rust submodule to latest main
stephen-derosa Apr 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,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 @@ -323,15 +324,20 @@ add_library(livekit SHARED
src/audio_processing_module.cpp
src/audio_source.cpp
src/audio_stream.cpp
src/data_track_frame.cpp
src/data_stream.cpp
src/data_track_error.cpp
src/data_track_stream.cpp
src/e2ee.cpp
src/ffi_handle.cpp
src/ffi_client.cpp
src/ffi_client.h
src/livekit.cpp
src/logging.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 @@ -683,10 +689,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
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,35 @@ CPP SDK is using clang C++ format
brew install clang-format
```


#### Memory Checks
Run valgrind on various examples or tests to check for memory leaks and other issues.
```bash
valgrind --leak-check=full ./build-debug/bin/livekit_integration_tests
valgrind --leak-check=full ./build-debug/bin/livekit_stress_tests
```

# 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-->
<br/><table>
<thead><tr><th colspan="2">LiveKit Ecosystem</th></tr></thead>
Expand Down
2 changes: 1 addition & 1 deletion client-sdk-rust
77 changes: 76 additions & 1 deletion examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ set(EXAMPLES_ALL
SimpleJoystickSender
SimpleJoystickReceiver
SimpleDataStream
PingPongPing
PingPongPong
HelloLivekitSender
HelloLivekitReceiver
LoggingLevelsBasicUsage
LoggingLevelsCustomSinks
BridgeRobot
Expand Down Expand Up @@ -242,6 +246,77 @@ add_custom_command(
$<TARGET_FILE_DIR:SimpleDataStream>/data
)

# --- ping_pong (request/response latency measurement over data tracks) ---

add_library(ping_pong_support STATIC
ping_pong/json_converters.cpp
ping_pong/json_converters.h
ping_pong/constants.h
ping_pong/messages.h
ping_pong/utils.h
)

target_include_directories(ping_pong_support PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/ping_pong
)

target_link_libraries(ping_pong_support
PRIVATE
nlohmann_json::nlohmann_json
)

add_executable(PingPongPing
ping_pong/ping.cpp
)

target_include_directories(PingPongPing PRIVATE ${EXAMPLES_PRIVATE_INCLUDE_DIRS})

target_link_libraries(PingPongPing
PRIVATE
ping_pong_support
livekit
spdlog::spdlog
)

add_executable(PingPongPong
ping_pong/pong.cpp
)

target_include_directories(PingPongPong PRIVATE ${EXAMPLES_PRIVATE_INCLUDE_DIRS})

target_link_libraries(PingPongPong
PRIVATE
ping_pong_support
livekit
spdlog::spdlog
)

# --- hello_livekit (minimal synthetic video + data publish / subscribe) ---

add_executable(HelloLivekitSender
hello_livekit/sender.cpp
)

target_include_directories(HelloLivekitSender PRIVATE ${EXAMPLES_PRIVATE_INCLUDE_DIRS})

target_link_libraries(HelloLivekitSender
PRIVATE
livekit
spdlog::spdlog
)

add_executable(HelloLivekitReceiver
hello_livekit/receiver.cpp
)

target_include_directories(HelloLivekitReceiver PRIVATE ${EXAMPLES_PRIVATE_INCLUDE_DIRS})

target_link_libraries(HelloLivekitReceiver
PRIVATE
livekit
spdlog::spdlog
)

# --- bridge_human_robot examples (robot + human; use livekit_bridge and SDL3) ---

add_executable(BridgeRobot
Expand Down Expand Up @@ -398,4 +473,4 @@ if(UNIX)
foreach(EXAMPLE ${EXAMPLES_BRIDGE})
add_dependencies(${EXAMPLE} copy_bridge_to_bin)
endforeach()
endif()
endif()
13 changes: 9 additions & 4 deletions examples/bridge_human_robot/human.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ static void renderFrame(const livekit::VideoFrame &frame) {
static std::atomic<uint64_t> g_audio_frames{0};
static std::atomic<uint64_t> g_video_frames{0};

constexpr const char *kRobotMicTrackName = "robot-mic";
constexpr const char *kRobotSimAudioTrackName = "robot-sim-audio";
constexpr const char *kRobotCamTrackName = "robot-cam";
constexpr const char *kRobotSimVideoTrackName = "robot-sim-frame";

int main(int argc, char *argv[]) {
// ----- Parse args / env -----
bool no_audio = false;
Expand Down Expand Up @@ -232,7 +237,7 @@ int main(int argc, char *argv[]) {

// ----- Set audio callbacks using Room::setOnAudioFrameCallback -----
room->setOnAudioFrameCallback(
"robot", livekit::TrackSource::SOURCE_MICROPHONE,
"robot", kRobotMicTrackName,
[playAudio, no_audio](const livekit::AudioFrame &frame) {
g_audio_frames.fetch_add(1, std::memory_order_relaxed);
if (!no_audio && g_selected_source.load(std::memory_order_relaxed) ==
Expand All @@ -242,7 +247,7 @@ int main(int argc, char *argv[]) {
});

room->setOnAudioFrameCallback(
"robot", livekit::TrackSource::SOURCE_SCREENSHARE_AUDIO,
"robot", kRobotSimAudioTrackName,
[playAudio, no_audio](const livekit::AudioFrame &frame) {
g_audio_frames.fetch_add(1, std::memory_order_relaxed);
if (!no_audio && g_selected_source.load(std::memory_order_relaxed) ==
Expand All @@ -253,7 +258,7 @@ int main(int argc, char *argv[]) {

// ----- Set video callbacks using Room::setOnVideoFrameCallback -----
room->setOnVideoFrameCallback(
"robot", livekit::TrackSource::SOURCE_CAMERA,
"robot", kRobotCamTrackName,
[](const livekit::VideoFrame &frame, std::int64_t /*timestamp_us*/) {
g_video_frames.fetch_add(1, std::memory_order_relaxed);
if (g_selected_source.load(std::memory_order_relaxed) ==
Expand All @@ -263,7 +268,7 @@ int main(int argc, char *argv[]) {
});

room->setOnVideoFrameCallback(
"robot", livekit::TrackSource::SOURCE_SCREENSHARE,
"robot", kRobotSimVideoTrackName,
[](const livekit::VideoFrame &frame, std::int64_t /*timestamp_us*/) {
g_video_frames.fetch_add(1, std::memory_order_relaxed);
if (g_selected_source.load(std::memory_order_relaxed) ==
Expand Down
130 changes: 130 additions & 0 deletions examples/hello_livekit/receiver.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* 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.
*/

/// Subscribes to the sender's camera video and data track. Run
/// HelloLivekitSender first; use the identity it prints, or the sender's known
/// participant name.
///
/// Usage:
/// HelloLivekitReceiver <ws-url> <receiver-token> <sender-identity>
///
/// Or via environment variables:
/// LIVEKIT_URL, LIVEKIT_RECEIVER_TOKEN, LIVEKIT_SENDER_IDENTITY

#include "livekit/livekit.h"

#include <atomic>
#include <chrono>
#include <csignal>
#include <cstdlib>
#include <thread>

using namespace livekit;

constexpr const char *kDataTrackName = "app-data";
constexpr const char *kVideoTrackName = "camera0";

std::atomic<bool> g_running{true};

void handleSignal(int) { g_running.store(false); }

std::string getenvOrEmpty(const char *name) {
const char *v = std::getenv(name);
return v ? std::string(v) : std::string{};
}

int main(int argc, char *argv[]) {
std::string url = getenvOrEmpty("LIVEKIT_URL");
std::string receiver_token = getenvOrEmpty("LIVEKIT_RECEIVER_TOKEN");
std::string sender_identity = getenvOrEmpty("LIVEKIT_SENDER_IDENTITY");

if (argc >= 4) {
url = argv[1];
receiver_token = argv[2];
sender_identity = argv[3];
}

if (url.empty() || receiver_token.empty() || sender_identity.empty()) {
LK_LOG_ERROR("Usage: HelloLivekitReceiver <ws-url> <receiver-token> "
"<sender-identity>\n"
" or set LIVEKIT_URL, LIVEKIT_RECEIVER_TOKEN, "
"LIVEKIT_SENDER_IDENTITY");
return 1;
}

std::signal(SIGINT, handleSignal);
#ifdef SIGTERM
std::signal(SIGTERM, handleSignal);
#endif

livekit::initialize(livekit::LogLevel::Info, livekit::LogSink::kConsole);

auto room = std::make_unique<Room>();
RoomOptions options;
options.auto_subscribe = true;
options.dynacast = false;

if (!room->Connect(url, receiver_token, options)) {
LK_LOG_ERROR("[receiver] Failed to connect");
livekit::shutdown();
return 1;
}

LocalParticipant *lp = room->localParticipant();
assert(lp);

LK_LOG_INFO("[receiver] Connected as identity='{}' room='{}'; subscribing "
"to sender identity='{}'",
lp->identity(), room->room_info().name, sender_identity);

int video_frame_count = 0;
room->setOnVideoFrameCallback(
sender_identity, kVideoTrackName,
[&video_frame_count](const VideoFrame &frame, std::int64_t timestamp_us) {
const auto ts_ms =
std::chrono::duration<double, std::milli>(timestamp_us).count();
const int n = video_frame_count++;
if (n % 10 == 0) {
LK_LOG_INFO("[receiver] Video frame #{} {}x{} ts_ms={}", n,
frame.width(), frame.height(), ts_ms);
}
});

int data_frame_count = 0;
room->addOnDataFrameCallback(
sender_identity, kDataTrackName,
[&data_frame_count](const std::vector<std::uint8_t> &payload,
std::optional<std::uint64_t> user_ts) {
const int n = data_frame_count++;
if (n % 10 == 0) {
LK_LOG_INFO("[receiver] Data frame #{}", n);
}
});

LK_LOG_INFO("[receiver] Listening for video track '{}' + data track '{}'; "
"Ctrl-C to exit",
kVideoTrackName, kDataTrackName);

while (g_running.load()) {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}

LK_LOG_INFO("[receiver] Shutting down");
room.reset();

livekit::shutdown();
return 0;
}
Loading
Loading