Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a995042
DataTracks integration through FFI
stephen-derosa Mar 20, 2026
257268d
compiling with new data tracks work
stephen-derosa Mar 19, 2026
3e66ac7
data tracks buffering is now handled on the rust side
stephen-derosa Mar 19, 2026
92dbca2
data tracks buffering is now handled on the rust side
stephen-derosa Mar 19, 2026
e1e6f67
replace cout/cerr with LK_LOG
stephen-derosa Mar 20, 2026
1d7579f
no longer need BridgeDataTrack()
stephen-derosa Mar 20, 2026
b393c32
create*Track -> publish*Track()
stephen-derosa Mar 20, 2026
a6ca226
dont add/remove stuff from bridge since we are targetting a branch wh…
stephen-derosa Mar 24, 2026
87e00b7
ActiveDataReader: subscriber mutex
stephen-derosa Mar 24, 2026
b237ce4
DataTrackSubscription: read() sends request for latest message
stephen-derosa Mar 24, 2026
609a624
move realsense-livekit out of this
stephen-derosa Mar 24, 2026
de3c30f
no changes to bridge
stephen-derosa Mar 24, 2026
f4f07ff
simple_status example
stephen-derosa Mar 24, 2026
a6791b7
multiple subscriptions
stephen-derosa Mar 24, 2026
f701056
hello_livekit example for a video track and data tracl
stephen-derosa Mar 25, 2026
88a9cfb
move data track callback functionality to subscription_thread_dispatcher
stephen-derosa Mar 25, 2026
e5c4362
hello_livekit: sender.cpp/receiver.cpp
stephen-derosa Mar 25, 2026
d19e539
some data_tracks tests
stephen-derosa Mar 25, 2026
dd0f986
test_data_tracks.cpp
stephen-derosa Mar 25, 2026
183eb7a
setOnAudio/VideoFrameCallback() change signature to use track name
stephen-derosa Mar 25, 2026
8fa0814
setOnAudio/VideoFrameCallback() addition signature to use track name
stephen-derosa Mar 25, 2026
0ffed0e
clean name
stephen-derosa Mar 25, 2026
2e1543b
testing for new API
stephen-derosa Mar 25, 2026
f8f7685
data_track_subscription: on pushFrame() called when rust sends new fr…
stephen-derosa Mar 25, 2026
953f20d
typo/year
stephen-derosa Mar 26, 2026
58c986a
copyright year
stephen-derosa Mar 26, 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
8 changes: 4 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 @@ -324,14 +325,17 @@ 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/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 +687,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
56 changes: 56 additions & 0 deletions 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
SimpleStatusProducer
SimpleStatusConsumer
HelloLivekitSender
HelloLivekitReceiver
LoggingLevelsBasicUsage
LoggingLevelsCustomSinks
BridgeRobot
Expand Down Expand Up @@ -242,6 +246,58 @@ add_custom_command(
$<TARGET_FILE_DIR:SimpleDataStream>/data
)

# --- simple_status (producer + consumer text stream on producer-status) ---

add_executable(SimpleStatusProducer
simple_status/producer.cpp
)

target_include_directories(SimpleStatusProducer PRIVATE ${EXAMPLES_PRIVATE_INCLUDE_DIRS})

target_link_libraries(SimpleStatusProducer
PRIVATE
livekit
spdlog::spdlog
)

add_executable(SimpleStatusConsumer
simple_status/consumer.cpp
)

target_include_directories(SimpleStatusConsumer PRIVATE ${EXAMPLES_PRIVATE_INCLUDE_DIRS})

target_link_libraries(SimpleStatusConsumer
PRIVATE
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
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